Repository: callahantiff/PheKnowLator (language: python)

Issue:
TODO: Publish Docker to Docker Hub
Publish Docker to [Docker Hub](https://hub.docker.com/), when creating v2.0.0 release.
Configure Google Cloud Storage
### TASK  
**Task Type:** [`PKT DATA DELIVERY`](https://github.com/callahantiff/PheKnowLator/projects/8#card-51636129)

Use [Google Cloud Storage](https://www.googleadservices.com/pagead/aclk?sa=L&ai=DChcSEwi7po-yyOLtAhXPwMAKHaHXAW8YABABGgJpbQ&ohost=www.google.com&cid=CAESQOD2LbD6PYbiY_dRGpdEviRONsIl_TIk8CnocpjUZhoV1IT8yDJhc0YhP8slczqDPY0MqQYQgqCUwLzv0fijB80&sig=AOD64_0SRUWhzfKj80R0vIgeDeSWnaXKDw&q&adurl&ved=2ahUKEwjAtISyyOLtAhWWWc0KHc6UCqkQ0Qx6BAgiEAE) in build to store each build's downloaded data and output knowledge graphs  
- Data used for each build  
- Built KGs  
- Built Embeddings  

#### TODO  
- [x] Create GCS Bucket for builds in GCP PheKnowLator bucket  (details [here](https://github.com/callahantiff/PheKnowLator/issues/70#issuecomment-750473635))  
- [x] Apply [Object Lifecycle Management](https://cloud.google.com/storage/docs/lifecycle) to GCS bucket    
- [x] 
Create script similar to [`google_cloud_storage_downloader.py`](https://github.com/callahantiff/OMOP2OBO/blob/master/google_cloud_storage_downloader.py) that can provides API access to Google Cloud Storage 
 

**Resources:**  
- [Python API Tutorials](https://realpython.com/tutorials/api/)  




Discussion / hints:
Update README for how to use project Docker. Good examples from @bill-baumgartner included below:

https://github.com/UCDenver-ccp/craft-shared-tasks/wiki/Concept-Annotation-Task-Evaluation

https://github.com/UCDenver-ccp/craft-shared-tasks/wiki/Building-the-CRAFT-evaluation-Docker-container
@bill-baumgartner  - I am drafting the content that we will ultimately add to the README for utilizing Docker. I thought I would run it by you here. Would you mind taking a look at let me know what you think?

**Notes**
- The DockerHub link is not yet active and will be updated with the correct link once our container is uploaded.  
- References to "above" will make sense once this information is added back to the README.

<br>

***  

### Docker  
In order to utilize the Dockerized version of the code, please make sure that you have downloaded the newest version of [Docker](https://docs.docker.com/get-docker/). There are two ways to utilize Docker with this repository:  
- Obtain the pre-built Docker container from [DockerHub]()  
- Build the Container  

**Build the Container**  
To build the PheKnowLator Docker container:
- Download a stable release of this repository or clone this repository to get the most up-to-date version  
- Unpack the repository downloaded (if necessary), then execute the following commands to build the container:

```bash
cd /path/to/PheKnowLator (Note, this is the directory containing the Dockerfile file)
docker build -t pkt:[VERSION] .
```

**Running the Container**  
The following code can be used to run PheKnowLator from outside of the container (after obtaining a prebuilt container or after building the container locally):  

```bash
docker run --name pkt_example --rm -ti -v /local/path/to/PheKnowLator/resources/knowledge_graphs/:/PheKnowLator/resources/knowledge_graphs/ pkt:2.0.0 --app subclass --kg full --nde yes --rel yes --owl no --kgm yes
``` 

_NOTES_:  
- The Docker container cannot write to an encrypted filesystem, however, so please make sure `/local/path/to/PheKnowLator/resources/knowledge_graphs` references a directory that is not encrypted    
- The example shown above builds a full version of the knowledge graph using the subclass construction approach with node metadata, inverse relations, and decoding of OWL classes. See the `Running the pkt Library` section for more information on the parameters that can be passed to PheKnowLator.

***

Looks good. I have three comments:
1) There is a missing dot at the end of the docker build command, it should be:
`docker build -t pkt:[VERSION] .`
2) The `docker run` command can work from any directory so "run the following code from the project root (i.e. PheKnowLator/):" is not necessary, and may not be possible if the user doesn't download the PheKnowLator code.
3) I think the Dockerfile needs to be changed at this point such that it doesn't download the code from GitHub, but instead uses the `COPY` command to import into the container. This way, any local changes to the code will be automatically imported. I think this is the expected behavior and may lead to confusion if we are instead pulling in the code from GitHub each time.
> Looks good. I have three comments:
> 
> 1. There is a missing dot at the end of the docker build command, it should be:
>    `docker build -t pkt:[VERSION] .`
> 2. The `docker run` command can work from any directory so "run the following code from the project root (i.e. PheKnowLator/):" is not necessary, and may not be possible if the user doesn't download the PheKnowLator code.
> 3. I think the Dockerfile needs to be changed at this point such that it doesn't download the code from GitHub, but instead uses the `COPY` command to import into the container. This way, any local changes to the code will be automatically imported. I think this is the expected behavior and may lead to confusion if we are instead pulling in the code from GitHub each time.

Thanks @bill-baumgartner! I have fixed the first two points and will work on the third. I need to think about what assumptions we will impose when utilizing the `COPY` approach. I will put it on the list for our meeting tomorrow. 
### TASK  
**Task Type:** [`INFRASTRUCTURE`](https://github.com/callahantiff/PheKnowLator/projects/8#card-51635998)
Publish Docker container to DockerHub  

#### TODO  
- [ ] Decide if a single Docker container is published  
- [ ] Publish container and update README  

**Questions:**  
- @bill-baumgartner - Do we need to have two versions 1 for the CI/CD and one that people can download independently and run on their own? Perhaps we can just publish one to DockerHub and that can fulfill both use cases? 
This is what I am proposing for organizing the GCS bucket:  

```
GCS bucket root/  
    |---- pheknowlator/
    |     |---- release_v.1.0/ ...
    |     |---- release_v.2.0/
    |     |     |---- *build_<<date>>/
    |     |     |     |---- data/
    |     |     |     |     |---- original_data/
    |     |     |     |     |---- processed_data/   
    |     |     |     |---- knowledge_graphs/  
    |     |     |     |     |---- subclass_builds/
    |     |     |     |     |     |---- relations_only/
    |     |     |     |     |     |     |---- owl/
    |     |     |     |     |     |     |---- owlnets/     
    |     |     |     |     |     |---- inverse_relations/
    |     |     |     |     |     |     |---- owl/
    |     |     |     |     |     |     |---- owlnets/     
    |     |     |     |     |---- instance_builds/
    |     |     |     |     |     |---- relations_only/
    |     |     |     |     |     |     |---- owl/
    |     |     |     |     |     |     |---- owlnets/     
    |     |     |     |     |     |---- inverse_relations/
    |     |     |     |     |     |     |---- owl/
    |     |     |     |     |     |     |---- owlnets/    
    |     |     |---- *build_<<date>>/ ...
```

For `release_v.1.0` data, I will update it once this work is complete and add files from past builds so that I am no longer responsible for maintaining them via my DropBox.

*meant to symbolize each monthly build

<br>

**GCS Permissions Setting:** I was thinking of setting the bucket `pheknowlator` directory and all subsequent directories as [nearline](https://cloud.google.com/storage/docs/storage-classes?_ga=2.259601736.-357560691.1606765671) to start and once we know what the usage pattern is we can adjust it.

<br> 

@bill-baumgartner - What do you think about this plan?

I like the directory structure.  Do you expect to host other data aside from KG builds here? If not, then the `knowledge_graph_builds/` directory is probably not required.

It looks as though we can specify the storage class on a per-object level, and we can use [Object Lifecycle Management](https://cloud.google.com/storage/docs/lifecycle) rules to change the storage class over time. So, a newly built KG could use the `Standard Storage` class initially, and then be downgraded to `Nearline Storage` after a period of time, e.g. 30 or 60 days.  
> I like the directory structure. Do you expect to host other data aside from KG builds here? If not, then the `knowledge_graph_builds/` directory is probably not required.

I was thinking about that too. I'm guessing not, if we wanted to store a primary docker container or something like that, it wold likely be at the release-level. I will modify the figure to remove the `knowledge_graph_builds/` directory.

<br>

> It looks as though we can specify the storage class on a per-object level, and we can use [Object Lifecycle Management](https://cloud.google.com/storage/docs/lifecycle) rules to change the storage class over time. So, a newly built KG could use the `Standard Storage` class initially, and then be downgraded to `Nearline Storage` after a period of time, e.g. 30 or 60 days.

Awesome, this is perfect. 

_____

✔️   I will go ahead and get this set-up now. It will allow me to start modifying/creating code we will need to support the three-task build plan we discussed yesterday.



Fix patch:
diff --git a/.dockerignore b/.dockerignore
index bf9d432da..11d5fbdd8 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -11,6 +11,9 @@
 !Dockerfile
 !entrypoint.sh
 
+##########################################
+## PHEKNOWLATOR (PKT_KG) DIRECTORIES ##
+!resources/
 
 ##########################################
 ## PHEKNOWLATOR (PKT_KG) PACKAGE FILES ##
@@ -24,27 +27,3 @@
 
 # scripts needed to run pkt
 !Main.py
-!resources/edge_source_list.txt
-!resources/ontology_source_list.txt
-!resources/resource_info.txt
-
-# dependencies - directories and files
-!resources/construction_approach/subclass_construction_map.pkl
-!resources/knowledge_graphs/*.owl
-!resources/node_data/*.txt
-!resources/owl_decoding/*.txt
-!resources/relations_data/*.txt
-
-
-##########################################
-## DATA NEEDED TO BUILD KNOWLEDGE GRAPH ##
-# external data sets used for identifier mapping (these data are referenced in resources/resource_info.txt)
-!resources/processed_data/DISEASE_DOID_MAP.txt
-!resources/processed_data/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt
-!resources/processed_data/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt
-!resources/processed_data/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt
-!resources/processed_data/HPA_GTEx_TISSUE_CELL_MAP.txt
-!resources/processed_data/MESH_CHEBI_MAP.txt
-!resources/processed_data/PHENOTYPE_HPO_MAP.txt
-!resources/processed_data/STRING_PRO_ONTOLOGY_MAP.txt
-!resources/processed_data/UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt
diff --git a/.github/workflows/build-qa.yml b/.github/workflows/build-qa.yml
index edc190bde..70799ef5c 100644
--- a/.github/workflows/build-qa.yml
+++ b/.github/workflows/build-qa.yml
@@ -1,6 +1,5 @@
-# WorkFlow Do: install Python dependencies, run tests, linter, and return code coverage
 name: Rosey the Robot
-on: [push, pull_request]
+on: [push, pull_request, release]
 jobs:
 
   build:
@@ -10,24 +9,20 @@ jobs:
       - uses: actions/checkout@v2
         with:
           fetch-depth: 0
-
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
           python-version: 3.6
-
       - name: Install Requirements and Dependencies
         run: |
           pip install --upgrade pip setuptools
           pip install -e .[test]
-
       - name: Prepare Environment
         run: |
           rm -rf .coverage
           coverage erase
           curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
           chmod +x ./cc-test-reporter
-
       - name: Run Tests and Generate Coverage Results
         run: |
           pytest --cov=pkt_kg --cov-report xml:coverage.xml
@@ -37,28 +32,47 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
-
       - name: Linting with Mypy
         run: mypy --strict-optional pkt_kg
-
       - name: Sonar Cloud Scanner
         uses: sonarsource/sonarcloud-github-action@master
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-
       - name: Codacy
         uses: codacy/codacy-coverage-reporter-action@master
         with:
           project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
           coverage-reports: coverage.xml
-
       - name: Code Climate
         uses: paambaati/codeclimate-action@v2.7.5
         env:
           CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
-
       - name: Coveralls
         run: coveralls
         env:
             GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  publish_docker_container:
+    if: github.event_name == 'release'
+    needs: build
+    name: Push Docker Image to Docker Hub
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set-up QEMU
+        uses: docker/setup-qemu-action@v1
+      - name: Set-up Docker Buildx
+        uses: docker/setup-buildx-action@v1
+      - name: Login to DockerHub
+        uses: docker/login-action@v1
+        with:
+          username: ${{ secrets.DOCKER_USERNAME }}
+          password: ${{ secrets.DOCKER_PASSWORD }}
+      - name: Build and Push Container to DockerHub
+        id: docker_build
+        uses: docker/build-push-action@v2
+        with:
+            push: true
+            tags: callahantiff/pheknowlator:latest
+      - name: Image Digest
+        run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.github/workflows/kg-build.yml b/.github/workflows/kg-build.yml
new file mode 100644
index 000000000..b70184178
--- /dev/null
+++ b/.github/workflows/kg-build.yml
@@ -0,0 +1,44 @@
+name: Build and Deploy to Google Compute Engine
+on: release
+env:
+  PROJECT_ID: ${{ secrets.GCE_PROJECT }}
+  GCE_INSTANCE: pkt-builder
+  GCE_INSTANCE_ZONE: us-central1-a
+
+jobs:
+  setup-build-publish-deploy:
+    name: Setup, Build, Publish, and Deploy
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v2
+      with:
+       ref: scripting_prebuild_notebooks
+
+    # Setup gcloud CLI
+    - uses: google-github-actions/setup-gcloud@master
+      with:
+        version: '290.0.1'
+        service_account_key: ${{ secrets.GCE_SA_KEY }}
+        project_id: ${{ secrets.GCE_PROJECT }}
+
+    # Configure Docker to use the gcloud command-line tool as a credential
+    # helper for authentication
+    - run: |-
+        gcloud --quiet auth configure-docker
+      # Build the Docker image
+    - name: Build
+      run: |-
+        docker build --tag "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA" .
+      # Push the Docker image to Google Container Registry
+    - name: Publish
+      run: |-
+        docker push "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA"
+    - name: Deploy
+      run: |-
+        gcloud compute instances update-container "$GCE_INSTANCE" \
+          --zone "$GCE_INSTANCE_ZONE" \
+          --container-image "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA"
+
+## Need to add instance start and stop commands
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f2e4be85c..525f89bb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,9 +34,9 @@ scratch*.py
 !/resources/embeddings/README.md
 !/resources/knowledge_graphs/OWL_NETS_Property_Types.txt
 !/resources/knowledge_graphs/README.md
+!/resources/node_data/README.md
 !/resources/ontologies/ontology_source_metadata.txt
 !/resources/ontologies/README.md
-!/resources/node_data/README.md
 !/resources/owl_decoding/README.md
 !/resources/relations_data/README.md
 
@@ -57,3 +57,9 @@ coverage.xml
 .notipy.json
 \.vscode/
 /tests/archive/
+
+# project api keys
+/resources/project_keys/*
+
+# builds data
+builds/temp/*
\ No newline at end of file
diff --git a/Data_Preparation.ipynb b/Data_Preparation.ipynb
index 570e11d55..c8073fe9a 100644
--- a/Data_Preparation.ipynb
+++ b/Data_Preparation.ipynb
@@ -7,8 +7,8 @@
    },
    "source": [
     "***\n",
-    "# PheKnowLator - Data Preparation\n",
     "***\n",
+    "# PheKnowLator - Data Preparation\n",
     "***\n",
     "\n",
     "**Author:** [TJCallahan](https://mail.google.com/mail/u/0/?view=cm&fs=1&tf=1&to=callahantiff@gmail.com)  \n",
@@ -27,8 +27,9 @@
     "\n",
     "<br>\n",
     "\n",
-    "**Dependencies:** This notebook utilizes several helper functions, which are stored in the [`data_preparation_helper_functions.py`](https://github.com/callahantiff/PheKnowLator/blob/master/scripts/python/data_preparation_helper_functions.py) script. Hyperlinks to all downloaded and generated data sources are provided on the [Data Sources](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources) Wiki page as well as within each source subsection of this notebook. All generated data is freely available for download from DropBox. \n",
-    "\n",
+    "**Dependencies:**   \n",
+    "- **Scripts**: This notebook utilizes several helper functions, which are stored in the [`data_utils.py`](https://github.com/callahantiff/PheKnowLator/blob/master/pkt_kg/utils/data_utils.py) and [`kg_utils.py`](https://github.com/callahantiff/PheKnowLator/blob/master/pkt_kg/utils/kg_utils.py) scripts.  \n",
+    "- **Data**: Hyperlinks to all downloaded and generated data sources are provided through [this](https://console.cloud.google.com/storage/browser/pheknowlator/release_v2.0.0?project=pheknowlator) dedicated Google Cloud Storage Bucket. <u>This notebook will download everything that is needed for you</u>.  \n",
     "_____\n",
     "***"
    ]
@@ -57,7 +58,6 @@
     "  - [Reactome Pathways - Pathway Ontology](#reactome-pw)  \n",
     "  - [Genomic Identifiers - Sequence Ontology](#genomic-soo)  \n",
     "\n",
-    "<br>\n",
     "\n",
     "### [Create Edge Datasets](#create-edge-datasets)\n",
     "- [ONTOLOGIES](#ontologies)  \n",
@@ -69,12 +69,12 @@
     "  - [Clinvar Variant-Diseases and Phenotypes](#clinvar-variant)\n",
     "  - [Uniprot Protein-Cofactor and Protein-Catalyst](#uniprot-protein-cofactorcatalyst)  \n",
     "\n",
-    "<br>\n",
     "\n",
     "### [Create Instance Data and/or Subclass Metadata](#create-instance-metadata)  \n",
     "- [Genes/RNA](#gene-and-rna-metadata)\n",
     "- [Pathways](#pathway-metadata)\n",
     "- [Variants](#variant-metadata) \n",
+    "- [Relations](#relations-metadata) \n",
     "\n",
     "____"
    ]
@@ -90,24 +90,35 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Set-Up Environment\n",
+    "## Set-Up Environment\n",
     "_____"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 75,
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# # uncomment and update to install any required modules\n",
+    "# import sys\n",
+    "# !{sys.executable} -m pip install <<MODULE NAME>>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
     "# import needed libraries\n",
     "import datetime\n",
     "import glob\n",
-    "import ijson\n",
     "import itertools\n",
     "import networkx\n",
     "import numpy\n",
     "import os\n",
+    "import openpyxl\n",
     "import pandas\n",
     "import pickle\n",
     "import requests\n",
@@ -115,40 +126,42 @@
     "\n",
     "from collections import Counter\n",
     "from functools import reduce\n",
-    "from owlready2 import subprocess\n",
     "from rdflib import Graph, Namespace, URIRef, BNode, Literal\n",
     "from rdflib.namespace import OWL, RDF, RDFS\n",
     "from reactome2py import content\n",
     "from tqdm import tqdm\n",
     "\n",
-    "# import script containing helper functions\n",
-    "from pkt_kg.utils import *"
+    "from pkt_kg.utils import *  # import pkt_kg utility script containing helper functions"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Define Global Variables**"
+    "#### Define Global Variables"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# directory to read unprocessed data files from\n",
+    "# directory to use for processing data\n",
     "unprocessed_data_location = 'resources/processed_data/unprocessed_data/'\n",
-    "\n",
-    "# directory to write processed data files to\n",
     "processed_data_location = 'resources/processed_data/'\n",
     "\n",
     "# directory to write relations data to\n",
     "relations_data_location = 'resources/relations_data/'\n",
     "\n",
     "# directory to write node metadata to\n",
-    "node_data_location = 'resources/node_data/'"
+    "node_data_location = 'resources/node_data/'\n",
+    "\n",
+    "# directory to write kg construction approach dictionary to\n",
+    "construction_approach_location = 'resources/construction_approach/'\n",
+    "\n",
+    "# directory to write ontology data to\n",
+    "ontology_data_location = 'resources/ontologies/'"
    ]
   },
   {
@@ -185,7 +198,7 @@
     "\n",
     "<br>\n",
     "\n",
-    "**Purpose:** To map create protein-coding gene-protein relations and mappings between the identifiers listed below. The edges types produced from each of these mappings will be further described within each identifier mapping section:  \n",
+    "**Purpose:** To map create `protein-coding gene`-`protein` edges and mappings between the identifiers types listed below. The edges types produced from each of these mappings will be further described within each of the subsequent identifier mapping sections:  \n",
     "- [Entrez Gene-Ensembl Transcript](#entrezgene-ensembltranscript)  \n",
     "- [Entrez Gene-Protein Ontology](#entrezgene-proteinontology)  \n",
     "- [Ensembl Gene-Entrez Gene](#ensemblgene-entrezgene)\n",
@@ -195,7 +208,7 @@
     "\n",
     "<br>\n",
     "\n",
-    "**Gene and Trainscript Types:** The transcript and gene/locus types were reviewed by a PhD Molecular biologist to confirm whether or not they should be treated as `protein-coding` or not, which is useful for creating `genomic-rna`, `genomic-protein`, and `rna-protein` edges in the knowledge graph. For more information on this classification, please see the table below. Definitions of concepts in the table have been taken from [HGNC](https://www.genenames.org/help/symbol-report/), [Ensembl](https://uswest.ensembl.org/info/genome/genebuild/biotypes.html), [NCBI](https://www.ncbi.nlm.nih.gov/IEB/ToolBox/CPP_DOC/lxr/source/src/objects/entrezgene/entrezgene.asn), and Wikipedia.\n",
+    "**Gene and Transcript Types:** The transcript and gene/locus types were reviewed by a PhD Molecular biologist to confirm whether or not they should be classified as `protein-coding` or not, which is useful for creating `genomic`-`rna`, `genomic`-`protein`, and `rna`-`protein` edges in the knowledge graph. For more information on this classification, please see the table below. Definitions of concepts in the table have been taken from [HGNC](https://www.genenames.org/help/symbol-report/), [Ensembl](https://uswest.ensembl.org/info/genome/genebuild/biotypes.html), [NCBI](https://www.ncbi.nlm.nih.gov/IEB/ToolBox/CPP_DOC/lxr/source/src/objects/entrezgene/entrezgene.asn), and Wikipedia.\n",
     "\n",
     "<table>\n",
     "<th align=\"center\">Gene and Transcript Type</th>  \n",
@@ -206,7 +219,7 @@
     "<th align=\"center\">Genomic material <i>has_gene_product</i> Protein</th>\n",
     "<tr>\n",
     "  <td rowspan=\"2\">biological-region</td> \n",
-    "  <td rowspan=\"2\">Biological_region (SO:0001411; Special note: This is a parental feature spanning all other feature annotation on each RefSeq Functional Element record. It is a 'misc_feature' in GenBank flat files but a 'Region' feature in ASN.1 and GFF3 formats</td>\n",
+    "  <td rowspan=\"2\">Biological_region (SO:0001411); Special note: This is a parental feature spanning all other feature annotation on each RefSeq Functional Element record. It is a 'misc_feature' in GenBank flat files but a 'Region' feature in ASN.1 and GFF3 formats</td>\n",
     "  <td>gene</td> \n",
     "  <td>yes</td> \n",
     "  <td>no</td> \n",
@@ -528,7 +541,7 @@
     "</tr>\n",
     "<tr>\n",
     "  <td rowspan=\"2\">pseudogene</td> \n",
-    "  <td rowspan=\"2\">Have homology to proteins but generally suffer from a disrupted coding sequence and an active homologous gene can be found at another locus. Sometimes these entries have an intact coding sequence or an open but truncated ORF, in which case there is other evidence used (for example genomic polyA stretches at the 3' end) to classify them as a pseudogene</td>\n",
+    "  <td rowspan=\"2\">Have homology to proteins but generally suffer from a disrupted coding sequence and an active homologous gene can be found at another locus. Sometimes these entries have an intact coding sequence or an open but truncated open reading frame, in which case there is other evidence used (for example genomic polyA stretches at the 3' end) to classify them as a pseudogene</td>\n",
     "  <td>gene</td> \n",
     "  <td>yes</td> \n",
     "  <td>no</td> \n",
@@ -584,7 +597,7 @@
     "</tr>\n",
     "<tr>\n",
     "  <td rowspan=\"2\">rRNA_pseudogene</td> \n",
-    "  <td rowspan=\"2\">A gene that has homology to known protein-coding genes but contain a frameshift and/or stop codon(s) which disrupts the ORF. Thought to have arisen through duplication followed by loss of function</td>\n",
+    "  <td rowspan=\"2\">A gene that has homology to known protein-coding genes but contain a frameshift and/or stop codon(s) which disrupts the open reading frame. Thought to have arisen through duplication followed by loss of function</td>\n",
     "  <td>gene</td> \n",
     "  <td>yes</td> \n",
     "  <td>no</td> \n",
@@ -598,7 +611,7 @@
     "</tr>\n",
     "<tr>\n",
     "  <td rowspan=\"2\">scaRNA</td> \n",
-    "  <td rowspan=\"2\">Small Cajal body-specific RNAs are a class of small nucleolar RNAs that specifically localise to the Cajal body, a nuclear organelle involved in the biogenesis of small nuclear ribonucleoproteins/td>\n",
+    "  <td rowspan=\"2\">Small Cajal body-specific RNAs are a class of small nucleolar RNAs that specifically localize to the Cajal body, a nuclear organelle involved in the biogenesis of small nuclear ribonucleoproteins/td>\n",
     "  <td>gene</td> \n",
     "  <td>yes</td> \n",
     "  <td>no</td> \n",
@@ -632,6 +645,7 @@
     "  <td>no</td> \n",
     "  <td>no</td>\n",
     "</tr>\n",
+    "<tr>\n",
     "  <td>transcript</td> \n",
     "  <td>yes</td> \n",
     "  <td>no</td> \n",
@@ -883,7 +897,7 @@
     "  <td>no</td> \n",
     "</tr>\n",
     "<tr>\n",
-    "  <td rowspan=\"2\" align=\"center\">VvaultRNA</td> \n",
+    "  <td rowspan=\"2\" align=\"center\">vaultRNA</td> \n",
     "  <td rowspan=\"2\" align=\"center\">Short non coding RNA genes that form part of the vault ribonucleoprotein complex</td>\n",
     "  <td>gene</td> \n",
     "  <td>yes</td> \n",
@@ -909,8 +923,8 @@
     "- UniProt Identifiers ➞ [`uniprot_identifier_mapping.tab`](https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Cdatabase(GeneID)%2Cdatabase(Ensembl)%2Cdatabase(HGNC)%2Cgenes(PREFERRED)%2Cgenes(ALTERNATIVE))\n",
     "\n",
     "*All Merged Data Sets:*  \n",
-    "- [`Merged_Human_Ensembl_Entrez_HGNC_Uniprot_Identifiers.txt`](https://www.dropbox.com/s/qnxqnm88rqcerh9/Merged_Human_Ensembl_Entrez_HGNC_Uniprot_Identifiers.txt?dl=1)  \n",
-    "- [`Merged_gene_rna_protein_identifiers.pkl`](https://www.dropbox.com/s/6idnt7b3i322hlh/Merged_gene_rna_protein_identifiers.pkl?dl=1)  \n",
+    "- `Merged_Human_Ensembl_Entrez_HGNC_Uniprot_Identifiers.txt` \n",
+    "- `Merged_gene_rna_protein_identifiers.pkl`  \n",
     "\n",
     "***"
    ]
@@ -919,40 +933,48 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "***\n",
-    "**HGNC Data** \n",
-    "\n",
-    "_Human Gene Set Data_ - `hgnc_complete_set.txt`"
+    "**Genomic Typing Dictionary**  \n",
+    "Read in the  `genomic_typing_dict.pkl` dictionary, which is needed in order to preprocess the genomic identifier datasets."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Data from FTP Server: ftp://ftp.ebi.ac.uk/pub/databases/genenames/new/tsv/hgnc_complete_set.txt\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "url = 'ftp://ftp.ebi.ac.uk/pub/databases/genenames/new/tsv/hgnc_complete_set.txt'\n",
-    "data_downloader(url, unprocessed_data_location)"
+    "# download data\n",
+    "url = 'https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/genomic_typing_dict.pkl'\n",
+    "if not os.path.exists(unprocessed_data_location + 'genomic_typing_dict.pkl'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "genomic_type_mapper = pickle.load(open(unprocessed_data_location + 'genomic_typing_dict.pkl', 'rb'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "**HGNC Data** \n",
+    "\n",
+    "_Human Gene Set Data_ - `hgnc_complete_set.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "hgnc = pandas.read_csv(unprocessed_data_location + 'hgnc_complete_set.txt',\n",
-    "                       header=0,\n",
-    "                       delimiter='\\t',\n",
-    "                       low_memory=False)"
+    "# download data\n",
+    "url = 'ftp://ftp.ebi.ac.uk/pub/databases/genenames/new/tsv/hgnc_complete_set.txt'\n",
+    "if not os.path.exists(unprocessed_data_location + 'hgnc_complete_set.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "hgnc = pandas.read_csv(unprocessed_data_location + 'hgnc_complete_set.txt', header=0, delimiter='\\t', low_memory=False)"
    ]
   },
   {
@@ -965,187 +987,36 @@
   },
   {
    "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>hgnc_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "      <th>ensembl_gene_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>5</td>\n",
-       "      <td>1</td>\n",
-       "      <td>ENSG00000121410</td>\n",
-       "      <td>P04217</td>\n",
-       "      <td>A1BG</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>19q13.43</td>\n",
-       "      <td>None</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>37133</td>\n",
-       "      <td>503538</td>\n",
-       "      <td>ENSG00000268895</td>\n",
-       "      <td>None</td>\n",
-       "      <td>A1BG-AS1</td>\n",
-       "      <td>lncRNA</td>\n",
-       "      <td>None</td>\n",
-       "      <td>19q13.43</td>\n",
-       "      <td>None</td>\n",
-       "      <td>ncRNA</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>24086</td>\n",
-       "      <td>29974</td>\n",
-       "      <td>ENSG00000148584</td>\n",
-       "      <td>Q9NQ94</td>\n",
-       "      <td>A1CF</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>10q11.23</td>\n",
-       "      <td>None</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  hgnc_id entrez_id  ensembl_gene_id uniprot_id    symbol  hgnc_gene_type  \\\n",
-       "0       5         1  ENSG00000121410     P04217      A1BG  protein-coding   \n",
-       "1   37133    503538  ENSG00000268895       None  A1BG-AS1          lncRNA   \n",
-       "2   24086     29974  ENSG00000148584     Q9NQ94      A1CF  protein-coding   \n",
-       "\n",
-       "   name map_location synonyms master_gene_type  \n",
-       "0  None     19q13.43     None   protein-coding  \n",
-       "1  None     19q13.43     None            ncRNA  \n",
-       "2  None     10q11.23     None   protein-coding  "
-      ]
-     },
-     "execution_count": 5,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# remove all rows thave don't have 'approved' status\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
     "hgnc = hgnc.loc[hgnc['status'].apply(lambda x: x == 'Approved')]\n",
-    "\n",
-    "# drop uneeded columns\n",
-    "hgnc = hgnc[['hgnc_id', 'entrez_id', 'ensembl_gene_id', 'uniprot_ids', 'symbol', 'locus_type',\n",
-    "             'alias_symbol', 'name', 'location', 'alias_name']]\n",
-    "\n",
-    "# rename columns\n",
+    "hgnc = hgnc[['hgnc_id', 'entrez_id', 'ensembl_gene_id', 'uniprot_ids', 'symbol', 'locus_type', 'alias_symbol', 'name', 'location', 'alias_name']]\n",
     "hgnc.rename(columns={'uniprot_ids': 'uniprot_id', 'location': 'map_location', 'locus_type': 'hgnc_gene_type'}, inplace=True)\n",
-    "\n",
-    "# strip 'HGNC' off of the identifiers\n",
-    "hgnc['hgnc_id'].replace('.*\\:','', inplace=True, regex=True)\n",
+    "hgnc['hgnc_id'].replace('.*\\:', '', inplace=True, regex=True)  # strip 'HGNC' off of the identifiers\n",
+    "hgnc.fillna('None', inplace=True)  # replace NaN with 'None'\n",
+    "hgnc['entrez_id'] = hgnc['entrez_id'].apply(lambda x: str(int(x)) if x != 'None' else 'None')  # make col str\n",
     "\n",
     "# combine certain columns into single column\n",
     "hgnc['name'] = hgnc['name'] + '|' + hgnc['alias_name']\n",
     "hgnc['synonyms'] = hgnc['alias_symbol'] + '|' + hgnc['alias_name']\n",
     "\n",
-    "# replace NaN with 'None'\n",
-    "hgnc.fillna('None', inplace=True)\n",
-    "\n",
-    "# make data columns of type string\n",
-    "hgnc['entrez_id'] = hgnc['entrez_id'].apply(lambda x: str(int(x)) if x != 'None' else 'None')\n",
-    "\n",
-    "# explode nested data\n",
+    "# explode nested data and reformat values in preparation for combining it with other gene identifiers\n",
     "explode_df_hgnc = explodes_data(hgnc.copy(), ['ensembl_gene_id', 'uniprot_id', 'symbol', 'name'], '|')\n",
     "\n",
     "# reformat hgnc gene type\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('region', 'biological region', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('complex locus constituent', 'complex locus constituent', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('endogenous retrovirus', 'endogenous retrovirus', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('fragile site', 'fragile site', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('immunoglobulin gene', 'IG_gene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('immunoglobulin pseudogene', 'IG_pseudogene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, long non-coding', 'lncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, micro', 'miRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, misc', 'miscRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('phenotype only', 'phenotype only', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('gene with protein product', 'protein-coding', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('protocadherin', 'protocadherin', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('readthrough', 'readthrough', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, ribosomal', 'rRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, small nucleolar', 'snoRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, small nuclear', 'snRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, cluster', 'sRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('T cell receptor gene', 'TR_gene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('T cell receptor pseudogene', 'TR_pseudogene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('transposable element', 'transposable element', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, transfer', 'tRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('unknown', 'unknown', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, vault', 'vaultRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('virus integration site', 'virus integration site', inplace=True, regex=False)\n",
-    "explode_df_hgnc['hgnc_gene_type'].replace('RNA, Y', 'Y RNA', inplace=True, regex=False)\n",
-    "\n",
-    "# master gene type\n",
+    "for val in genomic_type_mapper['hgnc_gene_type'].keys():\n",
+    "    explode_df_hgnc['hgnc_gene_type'].replace(val, genomic_type_mapper['hgnc_gene_type'][val], inplace=True)\n",
+    "\n",
+    "# reformat master hgnc gene type\n",
     "explode_df_hgnc['master_gene_type'] = explode_df_hgnc['hgnc_gene_type']\n",
-    "explode_df_hgnc['master_gene_type'].replace('biological region', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('complex locus constituent', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('endogenous retrovirus', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('fragile site', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('IG_gene', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('IG_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('lncRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('miRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('miscRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('phenotype only', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('protocadherin', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('readthrough', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('snoRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('snRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('sRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('TR_gene', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('TR_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('transposable element', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('tRNA', 'tRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('vaultRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('virus integration site', 'other', inplace=True, regex=False)\n",
-    "explode_df_hgnc['master_gene_type'].replace('Y RNA', 'ncRNA', inplace=True, regex=False)\n",
-    "\n",
-    "# remove original gene type column\n",
-    "explode_df_hgnc.drop(['alias_symbol', 'alias_name'], axis=1, inplace=True)\n",
+    "master_dict = genomic_type_mapper['hgnc_master_gene_type']\n",
+    "for val in master_dict.keys():\n",
+    "    explode_df_hgnc['master_gene_type'].replace(val, master_dict[val], inplace=True)\n",
     "\n",
-    "# remove duplicates\n",
+    "# post-process reformatted data\n",
+    "explode_df_hgnc.drop(['alias_symbol', 'alias_name'], axis=1, inplace=True)  # remove original gene type column\n",
     "explode_df_hgnc.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -1169,34 +1040,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Gzipped data from FTP Server: ftp://ftp.ensembl.org/pub/release-102/gtf/homo_sapiens/Homo_sapiens.GRCh38.102.gtf.gz\n",
-      "Decompressing and Writing Gzipped Data to File\n"
-     ]
-    }
-   ],
-   "source": [
-    "url = 'ftp://ftp.ensembl.org/pub/release-102/gtf/homo_sapiens/Homo_sapiens.GRCh38.102.gtf.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "ensembl_geneset = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.gtf',\n",
-    "                                  header = None,\n",
-    "                                  delimiter='\\t',\n",
-    "                                  skiprows=5,\n",
-    "                                  low_memory=False)"
+    "# download data\n",
+    "url = 'ftp://ftp.ensembl.org/pub/release-102/gtf/homo_sapiens/Homo_sapiens.GRCh38.102.gtf.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.gtf'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "ensembl_geneset = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.gtf', header = None, delimiter='\\t', skiprows=5, low_memory=False)"
    ]
   },
   {
@@ -1209,229 +1063,46 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 3010595/3010595 [03:08<00:00, 16012.62it/s]\n"
-     ]
-    },
-    {
-     "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "5  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "5  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \n",
-       "0                                None       pseudogene     not protein-coding  \n",
-       "1                processed_transcript       pseudogene         protein-coding  \n",
-       "5  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding  "
-      ]
-     },
-     "execution_count": 8,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# set list of data items to extract from column string\n",
-    "data_cols = ['gene_id', 'transcript_id', 'gene_name', 'gene_biotype', 'transcript_name', 'transcript_biotype']\n",
-    "\n",
-    "# loop over items contained in the string of column 8\n",
-    "cleaned_column = []\n",
-    "\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cleaned_col, data_cols = [], ['gene_id', 'transcript_id', 'gene_name', 'gene_biotype', 'transcript_name', 'transcript_biotype']\n",
     "for data_list in tqdm(list(ensembl_geneset[8])):\n",
     "    data_list = data_list if not data_list.endswith(';') else data_list[:-1]\n",
-    "    temp_data = [data_list.split('; ')[[x.split(' ')[0] for x in data_list.split('; ')].index(col)] if col in data_list else col + ' None' for col in data_cols]\n",
-    "    cleaned_column.append(temp_data) \n",
-    "\n",
-    "# update columns\n",
-    "ensembl_geneset['ensembl_gene_id'] = [x[0].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "ensembl_geneset['transcript_stable_id'] = [x[1].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "ensembl_geneset['symbol'] = [x[2].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "ensembl_geneset['ensembl_gene_type'] = [x[3].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "ensembl_geneset['transcript_name'] = [x[4].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "ensembl_geneset['ensembl_transcript_type'] = [x[5].split(' ')[-1].replace('\"', '') for x in cleaned_column]\n",
-    "\n",
-    "# replace NaN with 'None'\n",
+    "    temp_data = [data_list.split('; ')[[x.split(' ')[0] for x in data_list.split('; ')].index(col)]\n",
+    "                 if col in data_list else col + ' None' for col in data_cols]\n",
+    "    cleaned_col.append(temp_data)\n",
     "ensembl_geneset.fillna('None', inplace=True)\n",
     "\n",
+    "# update primary column values\n",
+    "ensembl_geneset['ensembl_gene_id'] = [x[0].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "ensembl_geneset['transcript_stable_id'] = [x[1].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "ensembl_geneset['symbol'] = [x[2].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "ensembl_geneset['ensembl_gene_type'] = [x[3].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "ensembl_geneset['transcript_name'] = [x[4].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "ensembl_geneset['ensembl_transcript_type'] = [x[5].split(' ')[-1].replace('\"', '') for x in cleaned_col]\n",
+    "\n",
     "# reformat ensembl gene type\n",
-    "ensembl_geneset['ensembl_gene_type'].replace('misc_RNA', 'miscRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['ensembl_gene_type'].replace('TEC', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['ensembl_gene_type'].replace('protein_coding', 'protein-coding', inplace=True, regex=False)\n",
+    "gene_dict = genomic_type_mapper['ensembl_gene_type']\n",
+    "for val in gene_dict.keys():\n",
+    "    ensembl_geneset['ensembl_gene_type'].replace(val, gene_dict[val], inplace=True)\n",
     "\n",
     "# reformat master gene type\n",
     "ensembl_geneset['master_gene_type'] = ensembl_geneset['ensembl_gene_type']\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_C_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_C_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_D_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_J_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_J_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_V_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('IG_V_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('lncRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('miRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('miscRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('Mt_rRNA', 'rRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('Mt_tRNA', 'tRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('polymorphic_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('processed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('ribozyme', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('rRNA_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('scaRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('scRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('snoRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('snRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('sRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_C_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_D_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_J_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_J_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_V_gene', 'other', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('TR_V_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('transcribed_processed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('transcribed_unitary_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('transcribed_unprocessed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('translated_processed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('translated_unprocessed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('unitary_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('unprocessed_pseudogene', 'pseudogene', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_gene_type'].replace('vault_RNA', 'ncRNA', inplace=True, regex=False)\n",
-    "ensembl_geneset['ensembl_gene_type'].replace('vault_RNA', 'vaultRNA', inplace=True, regex=False)\n",
+    "gene_dict = genomic_type_mapper['ensembl_master_gene_type']\n",
+    "for val in gene_dict.keys():\n",
+    "    ensembl_geneset['master_gene_type'].replace(val, gene_dict[val], inplace=True)\n",
     "\n",
     "# reformat master transcript type\n",
-    "ensembl_geneset['master_transcript_type'] = ensembl_geneset['ensembl_transcript_type']\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_C_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_C_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_D_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_J_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_J_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_V_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('IG_V_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('lncRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('miRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('misc_RNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('Mt_rRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('Mt_tRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('non_stop_decay', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('None', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('nonsense_mediated_decay', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('polymorphic_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('processed_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('processed_transcript', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('protein_coding', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('retained_intron', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('ribozyme', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('rRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('rRNA_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('scaRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('scRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('snoRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('snRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('sRNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TEC', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_C_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_D_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_J_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_J_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_V_gene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('TR_V_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('transcribed_processed_pseudogene', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('transcribed_unitary_pseudogene', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('transcribed_unprocessed_pseudogene', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('translated_processed_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('translated_unprocessed_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('unitary_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('unprocessed_pseudogene', 'protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('vault_RNA', 'not protein-coding', inplace=True, regex=False)\n",
-    "ensembl_geneset['master_transcript_type'].replace('unknown', 'not protein-coding', inplace=True, regex=False)\n",
     "ensembl_geneset['ensembl_transcript_type'].replace('vault_RNA', 'vaultRNA', inplace=True, regex=False)\n",
+    "ensembl_geneset['master_transcript_type'] = ensembl_geneset['ensembl_transcript_type']\n",
+    "trans_dict = genomic_type_mapper['ensembl_master_transcript_type']\n",
+    "for val in trans_dict.keys():\n",
+    "    ensembl_geneset['master_transcript_type'].replace(val, trans_dict[val], inplace=True)\n",
     "\n",
-    "# remove uneeded columns\n",
+    "# post-process reformatted data\n",
     "ensembl_geneset.drop(list(range(9)), axis=1, inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "ensembl_geneset.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -1456,47 +1127,23 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Gzipped data from FTP Server: ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.uniprot.tsv.gz\n",
-      "Decompressing and Writing Gzipped Data to File\n"
-     ]
-    }
-   ],
-   "source": [
-    "url_uniprot = 'ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.uniprot.tsv.gz'\n",
-    "data_downloader(url_uniprot, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "ensembl_uniprot = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.uniprot.tsv',\n",
-    "                                  header=0,\n",
-    "                                  delimiter='\\t',\n",
-    "                                  low_memory=False)\n",
+    "# download data\n",
+    "url_uniprot = 'ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.uniprot.tsv.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.uniprot.tsv'):\n",
+    "    data_downloader(url_uniprot, unprocessed_data_location)\n",
     "\n",
-    "# rename columns\n",
-    "ensembl_uniprot.rename(columns={'xref': 'uniprot_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)\n",
+    "# load data\n",
+    "ensembl_uniprot = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.uniprot.tsv', header=0, delimiter='\\t', low_memory=False)\n",
     "\n",
-    "# replace \"-\"\n",
+    "# preprocess data\n",
+    "ensembl_uniprot.rename(columns={'xref': 'uniprot_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)\n",
     "ensembl_uniprot.replace('-', 'None', inplace=True)\n",
-    "\n",
-    "# replace NaN with 'None'\n",
     "ensembl_uniprot.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove uneeded columns\n",
-    "ensembl_uniprot.drop(['db_name', 'info_type', 'source_identity', 'xref_identity', 'linkage_type'], axis=1, inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
+    "# ensembl_uniprot.drop(['db_name', 'info_type', 'source_identity', 'xref_identity', 'linkage_type'], axis=1, inplace=True)\n",
     "ensembl_uniprot.drop_duplicates(subset=None, keep='first', inplace=True)"
    ]
   },
@@ -1510,51 +1157,24 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Gzipped data from FTP Server: ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.entrez.tsv.gz\n",
-      "Decompressing and Writing Gzipped Data to File\n"
-     ]
-    }
-   ],
-   "source": [
-    "url_entrez = 'ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.entrez.tsv.gz'\n",
-    "data_downloader(url_entrez, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# read in ensembl-entrez data\n",
-    "ensembl_entrez = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.entrez.tsv',\n",
-    "                                 header=0,\n",
-    "                                 delimiter='\\t',\n",
-    "                                 low_memory=False)\n",
+    "# download data\n",
+    "url_entrez = 'ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.entrez.tsv.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.entrez.tsv'):\n",
+    "    data_downloader(url_entrez, unprocessed_data_location)\n",
     "\n",
-    "# rename columns\n",
-    "ensembl_entrez.rename(columns={'xref': 'entrez_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)\n",
+    "# load data\n",
+    "ensembl_entrez = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.GRCh38.102.entrez.tsv', header=0, delimiter='\\t', low_memory=False)\n",
     "\n",
-    "# remove all rows that are not dbname \"EntrezGene\"\n",
+    "# preprocess data\n",
+    "ensembl_entrez.rename(columns={'xref': 'entrez_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)\n",
     "ensembl_entrez = ensembl_entrez.loc[ensembl_entrez['db_name'].apply(lambda x: x == 'EntrezGene')]\n",
-    "\n",
-    "# replace \"-\"\n",
     "ensembl_entrez.replace('-', 'None', inplace=True)\n",
-    "\n",
-    "# replace NaN with 'None'\n",
     "ensembl_entrez.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove uneeded columns\n",
     "ensembl_entrez.drop(['db_name', 'info_type', 'source_identity', 'xref_identity', 'linkage_type'], axis=1, inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "ensembl_entrez.drop_duplicates(subset=None, keep='first', inplace=True)"
    ]
   },
@@ -1567,91 +1187,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000186092</td>\n",
-       "      <td>ENST00000641515</td>\n",
-       "      <td>ENSP00000493376</td>\n",
-       "      <td>A0A2U3U0J3</td>\n",
-       "      <td>79501</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000186092</td>\n",
-       "      <td>ENST00000335137</td>\n",
-       "      <td>ENSP00000334393</td>\n",
-       "      <td>Q8NH21</td>\n",
-       "      <td>79501</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000284733</td>\n",
-       "      <td>ENST00000426406</td>\n",
-       "      <td>ENSP00000409316</td>\n",
-       "      <td>Q6IEY1</td>\n",
-       "      <td>729759</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id protein_stable_id  uniprot_id  \\\n",
-       "0  ENSG00000186092      ENST00000641515   ENSP00000493376  A0A2U3U0J3   \n",
-       "1  ENSG00000186092      ENST00000335137   ENSP00000334393      Q8NH21   \n",
-       "2  ENSG00000284733      ENST00000426406   ENSP00000409316      Q6IEY1   \n",
-       "\n",
-       "  entrez_id  \n",
-       "0     79501  \n",
-       "1     79501  \n",
-       "2    729759  "
-      ]
-     },
-     "execution_count": 13,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "ensembl_annot = pandas.merge(ensembl_uniprot,\n",
-    "                             ensembl_entrez,\n",
-    "                             left_on=['ensembl_gene_id', 'transcript_stable_id', 'protein_stable_id'],\n",
-    "                             right_on=['ensembl_gene_id', 'transcript_stable_id', 'protein_stable_id'],\n",
-    "                             how='outer')\n",
-    "\n",
-    "# replace NaN with 'None'\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "merge_cols = ['ensembl_gene_id', 'transcript_stable_id', 'protein_stable_id']\n",
+    "ensembl_annot = pandas.merge(ensembl_uniprot, ensembl_entrez, on=merge_cols, how='outer')\n",
     "ensembl_annot.fillna('None', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -1667,125 +1208,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>727856</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name ensembl_transcript_type  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None                    None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202    processed_transcript   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-202    processed_transcript   \n",
-       "\n",
-       "  master_gene_type master_transcript_type protein_stable_id uniprot_id  \\\n",
-       "0       pseudogene     not protein-coding              None       None   \n",
-       "1       pseudogene         protein-coding              None       None   \n",
-       "2       pseudogene         protein-coding              None       None   \n",
-       "\n",
-       "  entrez_id  \n",
-       "0      None  \n",
-       "1     84771  \n",
-       "2    727856  "
-      ]
-     },
-     "execution_count": 14,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "ensembl = pandas.merge(ensembl_geneset,\n",
-    "                       ensembl_annot,\n",
-    "                       left_on = ['ensembl_gene_id', 'transcript_stable_id'],\n",
-    "                       right_on = ['ensembl_gene_id', 'transcript_stable_id'],\n",
-    "                       how='outer')\n",
-    "\n",
-    "# replace NaN with 'None'\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "merge_cols = ['ensembl_gene_id', 'transcript_stable_id']\n",
+    "ensembl = pandas.merge(ensembl_geneset, ensembl_annot, on=merge_cols, how='outer')\n",
     "ensembl.fillna('None', inplace=True)\n",
     "ensembl.replace('NA','None', inplace=True, regex=False)\n",
     "\n",
@@ -1803,14 +1231,11 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "ensembl.to_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt',\n",
-    "               header=True,\n",
-    "               sep='\\t',\n",
-    "               index=False)"
+    "ensembl.to_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt', header=True, sep='\\t', index=False)"
    ]
   },
   {
@@ -1840,19 +1265,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Creviewed%2Cdatabase(GeneID)%2Cdatabase(Ensembl)%2Cdatabase(HGNC)%2Cgenes(ALTERNATIVE)%2Cgenes(PREFERRED)&format=tab'\n",
-    "data_downloader(url, unprocessed_data_location, 'uniprot_identifier_mapping.tab')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 16,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "uniprot = pandas.read_csv(unprocessed_data_location + 'uniprot_identifier_mapping.tab',\n",
-    "                          header=0,\n",
-    "                          delimiter='\\t')"
+    "if not os.path.exists(unprocessed_data_location + 'uniprot_identifier_mapping.tab'):\n",
+    "    data_downloader(url, unprocessed_data_location, 'uniprot_identifier_mapping.tab')\n",
+    "\n",
+    "# load data\n",
+    "uniprot = pandas.read_csv(unprocessed_data_location + 'uniprot_identifier_mapping.tab', header=0, delimiter='\\t')"
    ]
   },
   {
@@ -1860,101 +1279,16 @@
    "metadata": {},
    "source": [
     "_Preprocess Data_  \n",
-    "Data file needs to be lightly cleaned before it can be merged with other data. This light cleaning includes renaming columns, replacing `NaN` with `None`, and unnesting `;` and `\" \"` delimited data."
+    "Data file needs to be lightly cleaned before it can be merged with other data. This light cleaning includes renaming columns, replacing `NaN` with `None`, and unnesting `\"|\"` delimited data."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
-   "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>uniprot_id</th>\n",
-       "      <th>Status</th>\n",
-       "      <th>entrez_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>symbol</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>O95825</td>\n",
-       "      <td>reviewed</td>\n",
-       "      <td>9946</td>\n",
-       "      <td>ENST00000381554</td>\n",
-       "      <td>2420</td>\n",
-       "      <td>4P11</td>\n",
-       "      <td>CRYZL1</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>O95825</td>\n",
-       "      <td>reviewed</td>\n",
-       "      <td>9946</td>\n",
-       "      <td>ENST00000420072</td>\n",
-       "      <td>2420</td>\n",
-       "      <td>4P11</td>\n",
-       "      <td>CRYZL1</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>Q9Y2J0</td>\n",
-       "      <td>reviewed</td>\n",
-       "      <td>22895</td>\n",
-       "      <td>ENST00000389385</td>\n",
-       "      <td>17056</td>\n",
-       "      <td>KIAA0985</td>\n",
-       "      <td>RPH3A</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  uniprot_id    Status entrez_id transcript_stable_id hgnc_id  synonyms  \\\n",
-       "0     O95825  reviewed      9946      ENST00000381554    2420      4P11   \n",
-       "1     O95825  reviewed      9946      ENST00000420072    2420      4P11   \n",
-       "2     Q9Y2J0  reviewed     22895      ENST00000389385   17056  KIAA0985   \n",
-       "\n",
-       "   symbol  \n",
-       "0  CRYZL1  \n",
-       "1  CRYZL1  \n",
-       "2   RPH3A  "
-      ]
-     },
-     "execution_count": 17,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "# replace NaN with 'None'\n",
-    "uniprot.fillna('None', inplace=True)\n",
-    "\n",
-    "# rename columns\n",
+    "uniprot.fillna('None', inplace=True)  # replace NaN with 'None'\n",
     "uniprot.rename(columns={'Entry': 'uniprot_id',\n",
     "                        'Cross-reference (GeneID)': 'entrez_id',\n",
     "                        'Ensembl transcript': 'transcript_stable_id',\n",
@@ -1962,14 +1296,13 @@
     "                        'Gene names  (synonym )': 'synonyms',\n",
     "                        'Gene names  (primary )' :'symbol'}, inplace=True)\n",
     "\n",
-    "# update space-delimited synonyms to a '|'\n",
+    "# update space-delimited synonyms to a pipe (i.e. '|')\n",
     "uniprot['synonyms'] = uniprot['synonyms'].apply(lambda x: '|'.join(x.split()) if x.isupper() else x)\n",
     "\n",
     "# explode nested data\n",
     "explode_df_uniprot = explodes_data(uniprot.copy(), ['transcript_stable_id', 'entrez_id', 'hgnc_id'], ';')\n",
     "explode_df_uniprot = explodes_data(explode_df_uniprot.copy(), ['symbol'], '|')\n",
     "\n",
-    "\n",
     "# strip out uniprot names\n",
     "explode_df_uniprot['transcript_stable_id'].replace('\\s.*','', inplace=True, regex=True)\n",
     "\n",
@@ -1991,33 +1324,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Gzipped data from FTP Server: ftp://ftp.ncbi.nih.gov/gene/DATA/GENE_INFO/Mammalia/Homo_sapiens.gene_info.gz\n",
-      "Decompressing and Writing Gzipped Data to File\n"
-     ]
-    }
-   ],
-   "source": [
-    "url = 'ftp://ftp.ncbi.nih.gov/gene/DATA/GENE_INFO/Mammalia/Homo_sapiens.gene_info.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "ncbi_gene = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.gene_info',\n",
-    "                            header=0,\n",
-    "                            delimiter='\\t',\n",
-    "                            low_memory=False)"
+    "# download data\n",
+    "url = 'ftp://ftp.ncbi.nih.gov/gene/DATA/GENE_INFO/Mammalia/Homo_sapiens.gene_info.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'Homo_sapiens.gene_info'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "ncbi_gene = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.gene_info', header=0, delimiter='\\t', low_memory=False)"
    ]
   },
   {
@@ -2030,143 +1347,36 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
-   "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>entrez_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>chromosome</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>Other_designations</th>\n",
-       "      <th>name</th>\n",
-       "      <th>entrez_gene_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>1</td>\n",
-       "      <td>A1BG</td>\n",
-       "      <td>A1B|ABG|GAB|HYST2477</td>\n",
-       "      <td>19</td>\n",
-       "      <td>19q13.43</td>\n",
-       "      <td>alpha-1B-glycoprotein|HEL-S-163pA|epididymis s...</td>\n",
-       "      <td>alpha-1-B glycoprotein</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>2</td>\n",
-       "      <td>A2M</td>\n",
-       "      <td>A2MD|CPAMD5|FWP007|S863-7</td>\n",
-       "      <td>12</td>\n",
-       "      <td>12p13.31</td>\n",
-       "      <td>alpha-2-macroglobulin|C3 and PZP-like alpha-2-...</td>\n",
-       "      <td>alpha-2-macroglobulin</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>8</th>\n",
-       "      <td>3</td>\n",
-       "      <td>A2MP1</td>\n",
-       "      <td>A2MP</td>\n",
-       "      <td>12</td>\n",
-       "      <td>12p13.31</td>\n",
-       "      <td>pregnancy-zone protein pseudogene</td>\n",
-       "      <td>alpha-2-macroglobulin pseudogene 1</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  entrez_id symbol                   synonyms chromosome map_location  \\\n",
-       "0         1   A1BG       A1B|ABG|GAB|HYST2477         19     19q13.43   \n",
-       "4         2    A2M  A2MD|CPAMD5|FWP007|S863-7         12     12p13.31   \n",
-       "8         3  A2MP1                       A2MP         12     12p13.31   \n",
-       "\n",
-       "                                  Other_designations  \\\n",
-       "0  alpha-1B-glycoprotein|HEL-S-163pA|epididymis s...   \n",
-       "4  alpha-2-macroglobulin|C3 and PZP-like alpha-2-...   \n",
-       "8                  pregnancy-zone protein pseudogene   \n",
-       "\n",
-       "                                 name entrez_gene_type master_gene_type  \n",
-       "0              alpha-1-B glycoprotein   protein-coding   protein-coding  \n",
-       "4               alpha-2-macroglobulin   protein-coding   protein-coding  \n",
-       "8  alpha-2-macroglobulin pseudogene 1       pseudogene       pseudogene  "
-      ]
-     },
-     "execution_count": 20,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "# remove all rows that are not human\n",
-    "ncbi_gene = ncbi_gene.loc[ncbi_gene['#tax_id'].apply(lambda x: x == 9606)]\n",
-    "\n",
-    "# replace \"-\" with \"None\"\n",
-    "ncbi_gene.replace('-','None', inplace=True)\n",
-    "\n",
-    "# rename columns before merging\n",
+    "# preprocess data\n",
+    "ncbi_gene = ncbi_gene.loc[ncbi_gene['#tax_id'].apply(lambda x: x == 9606)]  # remove non-human rows\n",
+    "ncbi_gene.replace('-', 'None', inplace=True)\n",
     "ncbi_gene.rename(columns={'GeneID': 'entrez_id', 'Symbol': 'symbol', 'Synonyms': 'synonyms'}, inplace=True)\n",
-    "\n",
-    "# combine symbol columns into single column\n",
     "ncbi_gene['symbol'] = ncbi_gene['Symbol_from_nomenclature_authority'] + '|' + ncbi_gene['symbol']\n",
     "ncbi_gene['name'] = ncbi_gene['Full_name_from_nomenclature_authority'] + '|' + ncbi_gene['description']\n",
     "\n",
     "# explode nested data\n",
     "explode_df_ncbi_gene = explodes_data(ncbi_gene.copy(), ['symbol', 'name'], '|')\n",
-    "\n",
-    "# make sure that merge columns are of same type\n",
     "explode_df_ncbi_gene['entrez_id'] = explode_df_ncbi_gene['entrez_id'].astype(str)\n",
     "\n",
     "# reformat entrez gene type\n",
     "explode_df_ncbi_gene['entrez_gene_type'] = explode_df_ncbi_gene['type_of_gene']\n",
-    "explode_df_ncbi_gene['entrez_gene_type'].replace('None', 'unknown', inplace=True, regex=False)\n",
-    "explode_df_ncbi_gene['entrez_gene_type'].replace('pseudo', 'pseudogene', inplace=True, regex=False)\n",
-    "explode_df_ncbi_gene['entrez_gene_type'].replace('biological-region', 'biological region', inplace=True, regex=False)\n",
-    "\n",
+    "gene_dict = genomic_type_mapper['entrez_gene_type']\n",
+    "for val in gene_dict.keys():\n",
+    "    explode_df_ncbi_gene['entrez_gene_type'].replace(val, gene_dict[val], inplace=True)\n",
     "# reformat master gene type\n",
     "explode_df_ncbi_gene['master_gene_type'] = explode_df_ncbi_gene['entrez_gene_type']\n",
-    "explode_df_ncbi_gene['master_gene_type'].replace('biological region', 'other', inplace=True, regex=False)\n",
-    "explode_df_ncbi_gene['master_gene_type'].replace('scRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_ncbi_gene['master_gene_type'].replace('snoRNA', 'ncRNA', inplace=True, regex=False)\n",
-    "explode_df_ncbi_gene['master_gene_type'].replace('snRNA', 'ncRNA', inplace=True, regex=False)\n",
+    "gene_dict = genomic_type_mapper['master_gene_type']\n",
+    "for val in gene_dict.keys():\n",
+    "    explode_df_ncbi_gene['master_gene_type'].replace(val, gene_dict[val], inplace=True)\n",
     "\n",
-    "# remove uneeded columns\n",
+    "# post-process reformatted data\n",
     "explode_df_ncbi_gene.drop(['type_of_gene', 'dbXrefs', 'description', 'Nomenclature_status', 'Modification_date',\n",
     "                           'LocusTag', '#tax_id', 'Full_name_from_nomenclature_authority', 'Feature_type',\n",
     "                           'Symbol_from_nomenclature_authority'], axis=1, inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "explode_df_ncbi_gene.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -2184,34 +1394,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Data from https://proconsortium.org/download/current/promapping.txt\n"
-     ]
-    }
-   ],
-   "source": [
-    "url = 'https://proconsortium.org/download/current/promapping.txt'\n",
-    "data_downloader(url, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 22,
-   "metadata": {
-    "scrolled": true
-   },
+   "execution_count": null,
+   "metadata": {},
    "outputs": [],
    "source": [
-    "pro_mapping = pandas.read_csv(unprocessed_data_location + 'promapping.txt',\n",
-    "                              header=None,\n",
-    "                              names=['pro_id', 'Entry', 'pro_mapping'],\n",
-    "                              delimiter='\\t')"
+    "# download data\n",
+    "url = 'https://proconsortium.org/download/current/promapping.txt'\n",
+    "if not os.path.exists(unprocessed_data_location + 'promapping.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "pro_map = pandas.read_csv(unprocessed_data_location + 'promapping.txt', header=None, names=['pro_id', 'entry', 'pro_mapping'], delimiter='\\t')"
    ]
   },
   {
@@ -2219,92 +1412,25 @@
    "metadata": {},
    "source": [
     "_Preprocess Data_  \n",
-    "Basic filtering to to include `Protein Ontology` mappings to `Uniprot` identifiers and cleaning to update formatting of accession values in order to remove `UniProtKB:` is performed."
+    "Basic filtering to to include `Protein Ontology` mappings to `Uniprot` identifiers and cleaning to update formatting of accession values (i.e. removing `UniProtKB:`)."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
-   "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>pro_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>6</th>\n",
-       "      <td>PR_000000005</td>\n",
-       "      <td>P37173</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>7</th>\n",
-       "      <td>PR_000000005</td>\n",
-       "      <td>P38438</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>8</th>\n",
-       "      <td>PR_000000005</td>\n",
-       "      <td>Q62312</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "         pro_id uniprot_id\n",
-       "6  PR_000000005     P37173\n",
-       "7  PR_000000005     P38438\n",
-       "8  PR_000000005     Q62312"
-      ]
-     },
-     "execution_count": 23,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# remove rows without 'UniProtKB'\n",
-    "pro_mapping = pro_mapping.loc[pro_mapping['Entry'].apply(lambda x: x.startswith('UniProtKB:'))] \n",
-    "\n",
-    "# replace PR: with PR_\n",
-    "pro_mapping['pro_id'].replace('PR:','PR_', inplace=True, regex=True)\n",
-    "\n",
-    "# remove identifier type, which appears before ':'\n",
-    "pro_mapping['Entry'].replace('(^\\w*\\:)','', inplace=True, regex=True)\n",
-    "\n",
-    "# rename columns before merging\n",
-    "pro_mapping.rename(columns={'Entry': 'uniprot_id'}, inplace=True)\n",
-    "\n",
-    "# remove uneeded columns\n",
-    "pro_mapping.drop(['pro_mapping'], axis=1, inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
-    "pro_mapping.drop_duplicates(subset=None, keep='first', inplace=True)\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pro_map = pro_map.loc[pro_map['entry'].apply(lambda x: x.startswith('Uni') and '_VAR' not in x and ', ' not in x)]  # keep 'UniProtKB' rows\n",
+    "pro_map = pro_map.loc[pro_map['pro_mapping'].apply(lambda x: x.startswith('exact'))] # keep exact mappings\n",
+    "pro_map['pro_id'].replace('PR:','PR_', inplace=True, regex=True)  # replace PR: with PR_\n",
+    "pro_map['entry'].replace('(^\\w*\\:)','', inplace=True, regex=True)  # remove id prefixes\n",
+    "pro_map.rename(columns={'entry': 'uniprot_id'}, inplace=True)  # rename columns before merging\n",
+    "pro_map.drop(['pro_mapping'], axis=1, inplace=True)  # remove uneeded columns\n",
+    "pro_map.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
-    "pro_mapping.head(n=3)"
+    "pro_map.head(n=3)"
    ]
   },
   {
@@ -2313,160 +1439,31 @@
    "source": [
     "***\n",
     "\n",
-    "**Merge Processed Data:** `hgnc` + `ensembl`"
+    "#### Merging Processed Genomic Identifier Data Sources  \n",
+    "Merging all of the genomic identifier data sources is needed in order to create a map that can be used to integrate the different genomic data sources."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "*Data Sources:* `hgnc` + `ensembl`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \\\n",
-       "0                                None       pseudogene     not protein-coding   \n",
-       "1                processed_transcript       pseudogene         protein-coding   \n",
-       "2  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding   \n",
-       "\n",
-       "  protein_stable_id uniprot_id entrez_id hgnc_id hgnc_gene_type  name  \\\n",
-       "0              None       None      None    None           None  None   \n",
-       "1              None       None     84771    None           None  None   \n",
-       "2              None       None     84771    None           None  None   \n",
-       "\n",
-       "  map_location synonyms  \n",
-       "0         None     None  \n",
-       "1         None     None  \n",
-       "2         None     None  "
-      ]
-     },
-     "execution_count": 24,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# merge uniprot and ncbi data\n",
-    "ensembl_hgnc_merged_data = pandas.merge(ensembl, explode_df_hgnc,\n",
-    "                                        left_on=['ensembl_gene_id', 'entrez_id', 'uniprot_id',\n",
-    "                                                 'master_gene_type', 'symbol'],\n",
-    "                                        right_on=['ensembl_gene_id', 'entrez_id', 'uniprot_id',\n",
-    "                                                  'master_gene_type', 'symbol'],\n",
-    "                                        how='outer')\n",
+   "execution_count": null,
+   "metadata": {
+    "code_folding": []
+   },
+   "outputs": [],
+   "source": [
+    "merge_cols = ['ensembl_gene_id', 'entrez_id', 'uniprot_id', 'master_gene_type', 'symbol']\n",
+    "ensembl_hgnc_merged_data = pandas.merge(ensembl, explode_df_hgnc, on=merge_cols, how='outer')\n",
     "\n",
-    "# replace NaN with 'None'\n",
+    "# clean up merged data\n",
     "ensembl_hgnc_merged_data.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "ensembl_hgnc_merged_data.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -2478,162 +1475,20 @@
    "metadata": {},
    "source": [
     "***\n",
-    "**Merge Processed Data:** `ensembl_hgnc_merged_data` + `explode_df_uniprot`"
+    "*Data Sources:* `ensembl_hgnc_merged_data` + `explode_df_uniprot`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>Status</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \\\n",
-       "0                                None       pseudogene     not protein-coding   \n",
-       "1                processed_transcript       pseudogene         protein-coding   \n",
-       "2  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding   \n",
-       "\n",
-       "  protein_stable_id uniprot_id entrez_id hgnc_id hgnc_gene_type  name  \\\n",
-       "0              None       None      None    None           None  None   \n",
-       "1              None       None     84771    None           None  None   \n",
-       "2              None       None     84771    None           None  None   \n",
-       "\n",
-       "  map_location synonyms Status  \n",
-       "0         None     None   None  \n",
-       "1         None     None   None  \n",
-       "2         None     None   None  "
-      ]
-     },
-     "execution_count": 25,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# merge uniprot and ncbi data\n",
-    "ensembl_hgnc_uniprot_merged_data = pandas.merge(ensembl_hgnc_merged_data, explode_df_uniprot,\n",
-    "                                                left_on=['entrez_id', 'hgnc_id', 'uniprot_id', 'transcript_stable_id', 'symbol','synonyms'],\n",
-    "                                                right_on=['entrez_id', 'hgnc_id', 'uniprot_id', 'transcript_stable_id', 'symbol','synonyms'],\n",
-    "                                                how='outer')\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "merge_cols = ['entrez_id', 'hgnc_id', 'uniprot_id', 'transcript_stable_id', 'symbol','synonyms']\n",
+    "ensembl_hgnc_uniprot_merged_data = pandas.merge(ensembl_hgnc_merged_data, explode_df_uniprot, on=merge_cols, how='outer')\n",
     "\n",
-    "# replace NaN with 'None'\n",
+    "# clean up merged data\n",
     "ensembl_hgnc_uniprot_merged_data.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "ensembl_hgnc_uniprot_merged_data.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -2645,175 +1500,22 @@
    "metadata": {},
    "source": [
     "***\n",
-    "**Merge Processed Data:** `ensembl_hgnc_uniprot_merged_data` + `Homo_sapiens.gene_info`"
+    "*Data Sources:* `ensembl_hgnc_uniprot_merged_data` + `Homo_sapiens.gene_info`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": null,
    "metadata": {
     "code_folding": []
    },
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>entrez_id</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>Status</th>\n",
-       "      <th>chromosome</th>\n",
-       "      <th>Other_designations</th>\n",
-       "      <th>entrez_gene_type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>84771</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \\\n",
-       "0                                None       pseudogene     not protein-coding   \n",
-       "1                processed_transcript       pseudogene         protein-coding   \n",
-       "2  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding   \n",
-       "\n",
-       "  protein_stable_id uniprot_id entrez_id hgnc_id hgnc_gene_type  name  \\\n",
-       "0              None       None      None    None           None  None   \n",
-       "1              None       None     84771    None           None  None   \n",
-       "2              None       None     84771    None           None  None   \n",
-       "\n",
-       "  map_location synonyms Status chromosome Other_designations entrez_gene_type  \n",
-       "0         None     None   None       None               None             None  \n",
-       "1         None     None   None       None               None             None  \n",
-       "2         None     None   None       None               None             None  "
-      ]
-     },
-     "execution_count": 26,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "ensembl_hgnc_uniprot_ncbi_merged_data = pandas.merge(ensembl_hgnc_uniprot_merged_data, explode_df_ncbi_gene,\n",
-    "                                                     left_on=['entrez_id', 'master_gene_type', 'symbol', 'synonyms', 'name', 'map_location'],\n",
-    "                                                     right_on=['entrez_id', 'master_gene_type', 'symbol', 'synonyms', 'name', 'map_location'],\n",
-    "                                                     how='outer')\n",
+   "outputs": [],
+   "source": [
+    "merge_cols = ['entrez_id', 'master_gene_type', 'symbol', 'synonyms', 'name', 'map_location']\n",
+    "ensembl_hgnc_uniprot_ncbi_merged_data = pandas.merge(ensembl_hgnc_uniprot_merged_data, explode_df_ncbi_gene, on=merge_cols, how='outer')\n",
     "\n",
-    "# replace NaN with 'None'\n",
+    "# clean up merged data\n",
     "ensembl_hgnc_uniprot_ncbi_merged_data.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "ensembl_hgnc_uniprot_ncbi_merged_data.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -2825,183 +1527,21 @@
    "metadata": {},
    "source": [
     "***\n",
-    "**Merge Processed Data:** `ensembl_hgnc_uniprot_ncbi_merged_data` + `promapping.txt`  "
+    "*Data Sources:* `ensembl_hgnc_uniprot_ncbi_merged_data` + `promapping.txt`  "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": null,
    "metadata": {
     "code_folding": []
    },
-   "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>...</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>Status</th>\n",
-       "      <th>chromosome</th>\n",
-       "      <th>Other_designations</th>\n",
-       "      <th>entrez_gene_type</th>\n",
-       "      <th>pro_id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>3 rows × 21 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \\\n",
-       "0                                None       pseudogene     not protein-coding   \n",
-       "1                processed_transcript       pseudogene         protein-coding   \n",
-       "2  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding   \n",
-       "\n",
-       "  protein_stable_id uniprot_id  ... hgnc_id hgnc_gene_type  name map_location  \\\n",
-       "0              None       None  ...    None           None  None         None   \n",
-       "1              None       None  ...    None           None  None         None   \n",
-       "2              None       None  ...    None           None  None         None   \n",
-       "\n",
-       "  synonyms Status chromosome Other_designations entrez_gene_type pro_id  \n",
-       "0     None   None       None               None             None   None  \n",
-       "1     None   None       None               None             None   None  \n",
-       "2     None   None       None               None             None   None  \n",
-       "\n",
-       "[3 rows x 21 columns]"
-      ]
-     },
-     "execution_count": 27,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "merged_data = pandas.merge(ensembl_hgnc_uniprot_ncbi_merged_data,\n",
-    "                           pro_mapping,\n",
-    "                           left_on='uniprot_id',\n",
-    "                           right_on='uniprot_id',\n",
-    "                           how='outer')\n",
+   "outputs": [],
+   "source": [
+    "merged_data = pandas.merge(ensembl_hgnc_uniprot_ncbi_merged_data, pro_map, on='uniprot_id', how='outer')\n",
     "\n",
-    "# replace NaN with 'None'\n",
+    "# clean up merged data\n",
     "merged_data.fillna('None', inplace=True)\n",
-    "\n",
-    "# remove duplicates\n",
     "merged_data.drop_duplicates(subset=None, keep='first', inplace=True)\n",
     "\n",
     "# preview data\n",
@@ -3018,175 +1558,11 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1122107/1122107 [00:00<00:00, 1495358.80it/s]\n"
-     ]
-    },
-    {
-     "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>ensembl_gene_id</th>\n",
-       "      <th>transcript_stable_id</th>\n",
-       "      <th>symbol</th>\n",
-       "      <th>ensembl_gene_type</th>\n",
-       "      <th>transcript_name</th>\n",
-       "      <th>ensembl_transcript_type</th>\n",
-       "      <th>master_gene_type</th>\n",
-       "      <th>master_transcript_type</th>\n",
-       "      <th>protein_stable_id</th>\n",
-       "      <th>uniprot_id</th>\n",
-       "      <th>...</th>\n",
-       "      <th>hgnc_id</th>\n",
-       "      <th>hgnc_gene_type</th>\n",
-       "      <th>name</th>\n",
-       "      <th>map_location</th>\n",
-       "      <th>synonyms</th>\n",
-       "      <th>Status</th>\n",
-       "      <th>chromosome</th>\n",
-       "      <th>Other_designations</th>\n",
-       "      <th>entrez_gene_type</th>\n",
-       "      <th>pro_id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>None</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000456328</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-202</td>\n",
-       "      <td>processed_transcript</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000223972</td>\n",
-       "      <td>ENST00000450305</td>\n",
-       "      <td>DDX11L1</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>DDX11L1-201</td>\n",
-       "      <td>transcribed_unprocessed_pseudogene</td>\n",
-       "      <td>pseudogene</td>\n",
-       "      <td>not protein-coding</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>...</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>None</td>\n",
-       "      <td>unknown</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>3 rows × 21 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   ensembl_gene_id transcript_stable_id   symbol  \\\n",
-       "0  ENSG00000223972                 None  DDX11L1   \n",
-       "1  ENSG00000223972      ENST00000456328  DDX11L1   \n",
-       "2  ENSG00000223972      ENST00000450305  DDX11L1   \n",
-       "\n",
-       "                    ensembl_gene_type transcript_name  \\\n",
-       "0  transcribed_unprocessed_pseudogene            None   \n",
-       "1  transcribed_unprocessed_pseudogene     DDX11L1-202   \n",
-       "2  transcribed_unprocessed_pseudogene     DDX11L1-201   \n",
-       "\n",
-       "              ensembl_transcript_type master_gene_type master_transcript_type  \\\n",
-       "0                             unknown       pseudogene     not protein-coding   \n",
-       "1                processed_transcript       pseudogene         protein-coding   \n",
-       "2  transcribed_unprocessed_pseudogene       pseudogene     not protein-coding   \n",
-       "\n",
-       "  protein_stable_id uniprot_id  ... hgnc_id hgnc_gene_type  name map_location  \\\n",
-       "0              None       None  ...    None        unknown  None         None   \n",
-       "1              None       None  ...    None        unknown  None         None   \n",
-       "2              None       None  ...    None        unknown  None         None   \n",
-       "\n",
-       "  synonyms Status chromosome Other_designations entrez_gene_type pro_id  \n",
-       "0     None   None       None               None          unknown   None  \n",
-       "1     None   None       None               None          unknown   None  \n",
-       "2     None   None       None               None          unknown   None  \n",
-       "\n",
-       "[3 rows x 21 columns]"
-      ]
-     },
-     "execution_count": 28,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "clean_dates = []\n",
-    "\n",
     "for x in tqdm(list(merged_data['symbol'])):\n",
     "    if '-' in x and len(x.split('-')[0]) < 3 and len(x.split('-')[1]) == 3:\n",
     "        clean_dates.append(x.split('-')[1].upper() + x.split('-')[0])\n",
@@ -3211,10 +1587,7 @@
     "merged_data_clean = merged_data.drop_duplicates(subset=None, keep='first')\n",
     "\n",
     "# write data\n",
-    "merged_data_clean.to_csv(processed_data_location + 'Merged_Human_Ensembl_Entrez_HGNC_Uniprot_Identifiers.txt',\n",
-    "                         header=True,\n",
-    "                         sep='\\t',\n",
-    "                         index=False)\n",
+    "merged_data_clean.to_csv(processed_data_location + 'Merged_Human_Ensembl_Entrez_HGNC_Uniprot_Identifiers.txt', header=True, sep='\\t', index=False)\n",
     "    \n",
     "# preview data\n",
     "merged_data_clean.head(n=3)"
@@ -3228,9 +1601,9 @@
     "**Create a Master Mapping Dictionary**  \n",
     "Although the above steps result in a `pandas.Dataframe` of the merged identifiers, there is still work needed in order to be able to obtain a complete mapping between the identifiers. For example, if you were to search for Entrez gene identifier `entrez_259234` you would find the following mappings: `entrez_259234-ENSG00000233316`, `entrez_259234-DSCR10`. If you only had `ENSG00000233316`, with the current data you would be unable to obtain the gene symbol without first mapping to the Entrez gene identifier. \n",
     "\n",
-    "To solve this problem, we build a master dictionary where the keys are `ensembl_gene_id`, `transcript_stable_id`, `symbol`, `protein_stable_id`, `uniprot_id`, `entrez_id`, `hgnc_id`, and `pro_id` identifiers and values are the list of identifiers that match to each identifier. It's important to note that there are several labeling identifiers (i.e. `name`, `chromosome`, `map_location`, `Other_designations`, `synonyms`, `transcript_name`, `*_gene_types`, and `trasnscript_type_update`), which will only be mapped when clustered against one of the primary identifier types (i.e. the keys described above).\n",
+    "To solve this problem, we build a master dictionary where the keys are `ensembl_gene_id`, `transcript_stable_id`, `protein_stable_id`, `uniprot_id`, `entrez_id`, `hgnc_id`, `pro_id`, and `symbol` identifiers and values are the list of genomic identifiers that match to each identifier. It's important to note that there are several labeling identifiers (i.e. `name`, `chromosome`, `map_location`, `Other_designations`, `synonyms`, `transcript_name`, `*_gene_types`, and `trasnscript_type_update`), which will only be mapped when clustered against one of the primary identifier types (i.e. the keys described above).\n",
     "\n",
-    "_Note_. The next chunk does a lot of heavy lifting and takes approximately ~90 minutes to run."
+    "_Note_. The next chunk does a lot of heavy lifting and takes approximately ~50 minutes to run."
    ]
   },
   {
@@ -3239,35 +1612,32 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# get all permutations of identifiers (e.g. ['entrez_id', 'ensembl_gene_id'])\n",
-    "identifiers = ['ensembl_gene_id', 'transcript_stable_id', 'protein_stable_id', 'uniprot_id', 'entrez_id',\n",
-    "               'hgnc_id', 'pro_id', 'symbol', 'synonyms', 'ensembl_gene_type', 'transcript_name',\n",
-    "               'ensembl_transcript_type', 'master_gene_type', 'master_transcript_type', 'hgnc_gene_type',\n",
-    "               'name', 'map_location', 'chromosome', 'Other_designations', 'entrez_gene_type']\n",
-    "\n",
-    "stop_point = 8\n",
+    "# reformat data to convert all nones, empty values, and unknowns to NaN\n",
+    "for col in merged_data_clean.columns:\n",
+    "    merged_data_clean[col] = merged_data_clean[col].apply(lambda x: '|'.join([i for i in x.split('|') if i != 'None']))\n",
+    "merged_data_clean.replace(to_replace=['None', '', 'unknown'], value=numpy.nan, inplace=True)\n",
+    "identifiers = [x for x in merged_data_clean.columns if x.endswith('_id')] + ['symbol']\n",
     "\n",
-    "# get list of data types that ignores subjects of a permutation pair that are metadata\n",
-    "identifier_list = [x for x in list(itertools.permutations(identifiers, 2)) if x[0] not in identifiers[stop_point:]]\n",
-    "\n",
-    "# create master dictionary of all identifiers\n",
+    "# convert data to dictionary\n",
     "master_dict = {}\n",
-    "\n",
-    "for ids in tqdm(identifier_list):\n",
-    "    maps = {k: [ids[1] + '_' + x for x in set(g[ids[1]].tolist()) if x != 'None'] for k,g in merged_data.groupby(ids[0])}\n",
-    "    for key in maps.keys():\n",
-    "        if ids[0] + '_' + key in master_dict.keys():\n",
-    "            master_dict[ids[0] + '_' + key] += maps[key]\n",
-    "        else:\n",
-    "            master_dict[ids[0] + '_' + key] = maps[key]\n"
+    "for idx in tqdm(identifiers):\n",
+    "    grouped_data = merged_data_clean.groupby(idx)\n",
+    "    grp_ids = set([x for x in list(grouped_data.groups.keys()) if x != numpy.nan])\n",
+    "    for grp in grp_ids:\n",
+    "        df = grouped_data.get_group(grp).dropna(axis=1, how='all')\n",
+    "        df_cols, key = df.columns, idx + '_' + grp\n",
+    "        val_df = [[col + '_' + x for x in set(df[col]) if isinstance(x, str)] for col in df_cols if col != idx]\n",
+    "        if len(val_df) > 0:\n",
+    "            if key in master_dict.keys(): master_dict[key] += [i for j in val_df for i in j if len(i) > 0]\n",
+    "            else: master_dict[key] = [i for j in val_df for i in j if len(i) > 0]  "
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Condensing Master Mapping Dictionary_  \n",
-    "Once we have all pairwise mapping data between all of the identifiers, the next step is to condense all information by identifier, into a format that can easily be used downstream."
+    "_Finalizing Master Mapping Dictionary_  \n",
+    "Then, we need to identify a master gene and transcript type for each entity because the last ran code chunk can result in several genes and transcripts with differing types (i.e. `protein-coding` or `not protein-coding`). The next step collects all information for each gene and transcript and performs a voting procedure to select a single primary gene and transcript type."
    ]
   },
   {
@@ -3276,37 +1646,26 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "reformatted_mapped_identifiers = {}\n",
-    "\n",
-    "# set globals\n",
-    "gene_type_var = 'master_gene_type'\n",
-    "transcript_type_var = 'master_transcript_type'\n",
-    "\n",
-    "for ident in tqdm(master_dict.keys()):\n",
-    "    identifier_info = set()\n",
-    "    \n",
-    "    for x_id in master_dict[ident]:\n",
-    "        # get all identifying information for all linked identifiers\n",
-    "        if not any(x for x in identifiers[stop_point:] if x_id.startswith(x)) and x_id in master_dict.keys():\n",
-    "            identifier_info |= set(master_dict[x_id])\n",
-    "        else:\n",
-    "            continue\n",
-    "\n",
-    "    # clean gene and transcript types mapped to multiple types\n",
-    "    gene_types = [x.split('_')[-1] for x in identifier_info if x.startswith(gene_type_var)]\n",
-    "    trans_types = [x.split('_')[-1] for x in identifier_info if x.startswith(transcript_type_var)]\n",
-    "    type_updates = []\n",
-    "\n",
-    "    for types in [gene_types if len(gene_types) > 0 else ['None'], trans_types if len(trans_types) > 0 else ['None']]:\n",
-    "        if 'protein-coding' in set(types):\n",
-    "            type_updates.append('protein-coding')\n",
-    "        else:\n",
-    "            type_updates.append('not protein-coding')\n",
-    "\n",
-    "    # update identifier set information\n",
-    "    identifier_info = [x for x in identifier_info if not x.startswith(gene_type_var) and not x.startswith(transcript_type_var)]\n",
-    "    identifier_info += ['gene_type_update_' + type_updates[0], 'transcript_type_update_' + type_updates[1]]\n",
-    "    reformatted_mapped_identifiers[ident] = identifier_info"
+    "reformatted_mapped_identifiers, gene_type, transcript_type = {}, 'gene_type', 'transcript_type'\n",
+    "for key, values in tqdm(master_dict.items()):\n",
+    "    g_type_list, t_type_list, g_types, t_types, identifier_info = [], [], [], [], []\n",
+    "    if any(x for x in ['ensembl_gene_id', 'entrez_id', 'hgnc_id', 'symbol'] if x in key):\n",
+    "        for idx in values:\n",
+    "            if gene_type in idx: g_types += [idx.split('_')[-1]]\n",
+    "            else: identifier_info += [idx]\n",
+    "        for i in [g_types if len(g_types) > 0 else ['None']]:\n",
+    "            g_type_list += ['protein-coding' if 'protein-coding' in i else 'not protein-coding']\n",
+    "        identifier_info += ['gene_type_update_' + max(set(g_type_list), key=g_type_list.count)]\n",
+    "    elif 'transcript' in key:   \n",
+    "        for idx in values:\n",
+    "            if transcript_type in idx: t_types += [idx.split('_')[-1]]\n",
+    "            else: identifier_info += [idx]\n",
+    "        for i in [t_types if len(t_types) > 0 else ['None']]:\n",
+    "            t_type_list += ['protein-coding' if 'protein-coding' in i else 'not protein-coding']\n",
+    "        identifier_info += ['transcript_type_update_' + max(set(t_type_list), key=t_type_list.count)]\n",
+    "    else:\n",
+    "        identifier_info = values  \n",
+    "    reformatted_mapped_identifiers[key] = identifier_info        "
    ]
   },
   {
@@ -3320,8 +1679,7 @@
     "filepath = processed_data_location + 'Merged_gene_rna_protein_identifiers.pkl'\n",
     "\n",
     "# defensive way to write pickle.write, allowing for very large files on all platforms\n",
-    "max_bytes = 2**31 - 1\n",
-    "bytes_out = pickle.dumps(reformatted_mapped_identifiers)\n",
+    "max_bytes, bytes_out = 2**31 - 1, pickle.dumps(reformatted_mapped_identifiers)\n",
     "n_bytes = sys.getsizeof(bytes_out)\n",
     "\n",
     "with open(filepath, 'wb') as f_out:\n",
@@ -3331,8 +1689,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
-   "metadata": {},
+   "execution_count": null,
+   "metadata": {
+    "code_folding": [
+     0
+    ]
+   },
    "outputs": [],
    "source": [
     "# # load data\n",
@@ -3359,25 +1721,16 @@
     "### Ensembl Gene-Entrez Gene <a class=\"anchor\" id=\"ensemblgene-entrezgene\"></a>\n",
     "\n",
     "\n",
-    "**Purpose:** To map Ensembl gene identifiers to Entrez gene identifiers when creating the following edges:   \n",
-    "- gene-gene\n",
+    "**Purpose:** To map Ensembl gene identifiers to Entrez gene identifiers when creating `gene`-`gene` edges\n",
     "\n",
-    "**Output:** [`ENSEMBL_GENE_ENTREZ_GENE_MAP.txt`](https://www.dropbox.com/s/ggnue4s5psvywn9/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt?dl=1)"
+    "**Output:** `ENSEMBL_GENE_ENTREZ_GENE_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:51<00:00, 10539.92it/s] \n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'ENSEMBL_GENE_ENTREZ_GENE_MAP.txt',\n",
@@ -3385,116 +1738,18 @@
     "                  'gene_type_update', False, False)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 31,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 45312 ensembl gene-entrez gene edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "egeg_data = pandas.read_csv(processed_data_location + 'ENSEMBL_GENE_ENTREZ_GENE_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Ensembl_Gene_IDs', 'Entrez_Gene_IDs', 'Gene_Type'],\n",
-    "                            delimiter='\\t')\n",
+    "                            header=None, delimiter='\\t', low_memory=False,\n",
+    "                            names=['Ensembl_Gene_IDs', 'Entrez_Gene_IDs', 'Gene_Type'])\n",
     "\n",
-    "print('There are {edge_count} ensembl gene-entrez gene edges'.format(edge_count=len(egeg_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 32,
-   "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>Ensembl_Gene_IDs</th>\n",
-       "      <th>Entrez_Gene_IDs</th>\n",
-       "      <th>Gene_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSG00000000003</td>\n",
-       "      <td>7105</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSG00000000005</td>\n",
-       "      <td>64102</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSG00000000419</td>\n",
-       "      <td>8813</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>ENSG00000000457</td>\n",
-       "      <td>57147</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>ENSG00000000460</td>\n",
-       "      <td>55732</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Ensembl_Gene_IDs  Entrez_Gene_IDs       Gene_Type\n",
-       "0  ENSG00000000003             7105  protein-coding\n",
-       "1  ENSG00000000005            64102  protein-coding\n",
-       "2  ENSG00000000419             8813  protein-coding\n",
-       "3  ENSG00000000457            57147  protein-coding\n",
-       "4  ENSG00000000460            55732  protein-coding"
-      ]
-     },
-     "execution_count": 32,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} ensembl gene-entrez gene edges'.format(edge_count=len(egeg_data)))\n",
     "egeg_data.head(n=5)"
    ]
   },
@@ -3505,25 +1760,16 @@
     "***\n",
     "### Ensembl Transcript-Protein Ontology <a class=\"anchor\" id=\"ensembltranscript-proteinontology\"></a>\n",
     "\n",
-    "**Purpose:** To map Ensembl transcript identifiers to Protein Ontology identifiers when creating the following edges: \n",
-    "- rna-protein  \n",
+    "**Purpose:** To map Ensembl transcript identifiers to Protein Ontology identifiers when creating `rna`-`protein` edges\n",
     "\n",
-    "**Output:** [`ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt`](https://www.dropbox.com/s/9upjijohu0xzf13/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt?dl=1)\n"
+    "**Output:** `ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt`\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:56<00:00, 10115.52it/s]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt',\n",
@@ -3532,116 +1778,17 @@
    ]
   },
   {
-   "cell_type": "markdown",
+   "cell_type": "code",
+   "execution_count": null,
    "metadata": {},
+   "outputs": [],
    "source": [
-    "_Preview Processed Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 34,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 738355 ensembl transcript-protein ontology edges\n"
-     ]
-    }
-   ],
-   "source": [
+    "# load data, print the number of rows, and preview it\n",
     "etpr_data = pandas.read_csv(processed_data_location + 'ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Ensembl_Transcript_IDs', 'Protein_Ontology_IDs', 'Transcript_Type'],\n",
-    "                            delimiter='\\t',\n",
-    "                            low_memory=False)\n",
+    "                            header=None, delimiter='\\t', low_memory=False,\n",
+    "                            names=['Ensembl_Transcript_IDs', 'Protein_Ontology_IDs', 'Transcript_Type'])\n",
     "\n",
-    "print('There are {edge_count} ensembl transcript-protein ontology edges'.format(edge_count=len(etpr_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 35,
-   "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>Ensembl_Transcript_IDs</th>\n",
-       "      <th>Protein_Ontology_IDs</th>\n",
-       "      <th>Transcript_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENST00000000233</td>\n",
-       "      <td>PR_P84085</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENST00000000233</td>\n",
-       "      <td>PR_000004204</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENST00000000412</td>\n",
-       "      <td>PR_P20645</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>ENST00000000412</td>\n",
-       "      <td>PR_000010032</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>ENST00000000442</td>\n",
-       "      <td>PR_P11474-1</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Ensembl_Transcript_IDs Protein_Ontology_IDs Transcript_Type\n",
-       "0        ENST00000000233            PR_P84085  protein-coding\n",
-       "1        ENST00000000233         PR_000004204  protein-coding\n",
-       "2        ENST00000000412            PR_P20645  protein-coding\n",
-       "3        ENST00000000412         PR_000010032  protein-coding\n",
-       "4        ENST00000000442          PR_P11474-1  protein-coding"
-      ]
-     },
-     "execution_count": 35,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} ensembl transcript-protein ontology edges'.format(edge_count=len(etpr_data)))\n",
     "etpr_data.head(n=5)"
    ]
   },
@@ -3652,25 +1799,16 @@
     "***\n",
     "### Entrez Gene-Ensembl Transcript <a class=\"anchor\" id=\"entrezgene-ensembltranscript\"></a>\n",
     "\n",
-    "**Purpose:** To map entrez gene identifiers to Ensembl transcript identifiers when creating the following edges: \n",
-    "- gene-rna \n",
+    "**Purpose:** To map entrez gene identifiers to Ensembl transcript identifiers when creating `gene`-`rna` edges\n",
     "\n",
-    "**Output:** [`ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt`](https://www.dropbox.com/s/jxm9v7qfwm2b6ot/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt?dl=1)"
+    "**Output:** `ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:49<00:00, 10726.49it/s] \n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt',\n",
@@ -3678,123 +1816,18 @@
     "                  'transcript_type_update', False, False)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 37,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 227049 entrez gene identifiers-ensembl transcript edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "eet_data = pandas.read_csv(processed_data_location + 'ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt',\n",
-    "                           header=None,\n",
-    "                           names=['Entrez_Gene_IDs', 'Ensembl_Transcript_IDs', 'Gene_Type', 'Transcript_Type'],\n",
-    "                           delimiter='\\t',\n",
-    "                           low_memory=False)\n",
+    "                           header=None, delimiter='\\t', low_memory=False,\n",
+    "                           names=['Entrez_Gene_IDs', 'Ensembl_Transcript_IDs', 'Gene_Type', 'Transcript_Type'])\n",
     "\n",
-    "print('There are {edge_count} entrez gene identifiers-ensembl transcript edges'.format(edge_count=len(eet_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 38,
-   "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>Entrez_Gene_IDs</th>\n",
-       "      <th>Ensembl_Transcript_IDs</th>\n",
-       "      <th>Gene_Type</th>\n",
-       "      <th>Transcript_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>381</td>\n",
-       "      <td>ENST00000000233</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>4074</td>\n",
-       "      <td>ENST00000000412</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>2101</td>\n",
-       "      <td>ENST00000000442</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>2288</td>\n",
-       "      <td>ENST00000001008</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>56603</td>\n",
-       "      <td>ENST00000001146</td>\n",
-       "      <td>protein-coding</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   Entrez_Gene_IDs Ensembl_Transcript_IDs       Gene_Type  Transcript_Type\n",
-       "0              381        ENST00000000233  protein-coding              NaN\n",
-       "1             4074        ENST00000000412  protein-coding              NaN\n",
-       "2             2101        ENST00000000442  protein-coding              NaN\n",
-       "3             2288        ENST00000001008  protein-coding              NaN\n",
-       "4            56603        ENST00000001146  protein-coding              NaN"
-      ]
-     },
-     "execution_count": 38,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} entrez gene identifiers-ensembl transcript edges'.format(edge_count=len(eet_data)))\n",
     "eet_data.head(n=5)"
    ]
   },
@@ -3809,22 +1842,14 @@
     "- chemical-protein  \n",
     "- gene-protein\n",
     "\n",
-    "**Output:** [`ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt`](https://www.dropbox.com/s/e5x7rq4kc2kfq49/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt?dl=1)"
+    "**Output:** `ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 39,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:45<00:00, 11139.10it/s]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt',\n",
@@ -3832,116 +1857,18 @@
     "                  'gene_type_update', False, True)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 40,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 67635 entrez gene-protein ontology edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "egpr_data = pandas.read_csv(processed_data_location + 'ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Gene_IDs', 'Protein_Ontology_IDs', 'Gene_Type'],\n",
-    "                            delimiter='\\t')\n",
+    "                            header=None, delimiter='\\t', low_memory=False,\n",
+    "                            names=['Gene_IDs', 'Protein_Ontology_IDs', 'Gene_Type'])\n",
     "\n",
-    "print('There are {edge_count} entrez gene-protein ontology edges'.format(edge_count=len(egpr_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 41,
-   "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>Gene_IDs</th>\n",
-       "      <th>Protein_Ontology_IDs</th>\n",
-       "      <th>Gene_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>1</td>\n",
-       "      <td>PR_000003511</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>1</td>\n",
-       "      <td>PR_P04217</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>1</td>\n",
-       "      <td>PR_P04217-1</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>10</td>\n",
-       "      <td>PR_000011001</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>10</td>\n",
-       "      <td>PR_P11245</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   Gene_IDs Protein_Ontology_IDs       Gene_Type\n",
-       "0         1         PR_000003511  protein-coding\n",
-       "1         1            PR_P04217  protein-coding\n",
-       "2         1          PR_P04217-1  protein-coding\n",
-       "3        10         PR_000011001  protein-coding\n",
-       "4        10            PR_P11245  protein-coding"
-      ]
-     },
-     "execution_count": 41,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} entrez gene-protein ontology edges'.format(edge_count=len(egpr_data)))\n",
     "egpr_data.head(n=5)"
    ]
   },
@@ -3957,22 +1884,14 @@
     "- rna-anatomy  \n",
     "- rna-cell  \n",
     "\n",
-    "**Output:** [`GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt`](https://www.dropbox.com/s/7wdrbpc79kj3sr7/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt?dl=1)"
+    "**Output:** `GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 42,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:47<00:00, 10969.45it/s] \n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt',\n",
@@ -3980,116 +1899,18 @@
     "                  'transcript_type_update', False, False)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 43,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 278100 gene symbol-ensembl transcript edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "set_data = pandas.read_csv(processed_data_location + 'GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Gene_Symbols', 'Ensembl_Transcript_IDs', 'Transcript_Type'],\n",
-    "                            delimiter='\\t')\n",
+    "                           header=None, delimiter='\\t', low_memory=False,\n",
+    "                           names=['Gene_Symbols', 'Ensembl_Transcript_IDs', 'Transcript_Type'])\n",
     "\n",
-    "print('There are {edge_count} gene symbol-ensembl transcript edges'.format(edge_count=len(set_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 44,
-   "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>Gene_Symbols</th>\n",
-       "      <th>Ensembl_Transcript_IDs</th>\n",
-       "      <th>Transcript_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ARF5</td>\n",
-       "      <td>ENST00000000233</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>M6PR</td>\n",
-       "      <td>ENST00000000412</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ESRRA</td>\n",
-       "      <td>ENST00000000442</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>FKBP4</td>\n",
-       "      <td>ENST00000001008</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>CYP26B1</td>\n",
-       "      <td>ENST00000001146</td>\n",
-       "      <td>protein-coding</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Gene_Symbols Ensembl_Transcript_IDs Transcript_Type\n",
-       "0         ARF5        ENST00000000233  protein-coding\n",
-       "1         M6PR        ENST00000000412  protein-coding\n",
-       "2        ESRRA        ENST00000000442  protein-coding\n",
-       "3        FKBP4        ENST00000001008  protein-coding\n",
-       "4      CYP26B1        ENST00000001146  protein-coding"
-      ]
-     },
-     "execution_count": 44,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} gene symbol-ensembl transcript edges'.format(edge_count=len(set_data.drop_duplicates())))\n",
     "set_data.head(n=5)"
    ]
   },
@@ -4101,25 +1922,16 @@
     "\n",
     "### STRING-Protein Ontology <a class=\"anchor\" id=\"string-proteinontology\"></a>\n",
     "\n",
-    "**Purpose:** To map STRING identifiers to Protein Ontology identifiers when creating the following edges:   \n",
-    "- protein-protein  \n",
+    "**Purpose:** To map STRING identifiers to Protein Ontology identifiers when creating `protein`-`protein` edges \n",
     "\n",
-    "**Output:** [`STRING_PRO_ONTOLOGY_MAP.txt`](https://www.dropbox.com/s/mpv6rzum0c1lgxe/STRING_PRO_ONTOLOGY_MAP.txt?dl=0=1)"
+    "**Output:** `STRING_PRO_ONTOLOGY_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 45,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [00:50<00:00, 23545.76it/s] \n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'STRING_PRO_ONTOLOGY_MAP.txt',\n",
@@ -4127,116 +1939,18 @@
     "                  None, False, True)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 46,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 484756 string-protein ontology edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "stpr_data = pandas.read_csv(processed_data_location + 'STRING_PRO_ONTOLOGY_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['STRING_IDs', 'Protein_Ontology_IDs', 'Genomic_Type'],\n",
-    "                            delimiter='\\t')\n",
+    "                            header=None, delimiter='\\t', low_memory=False,\n",
+    "                            names=['STRING_IDs', 'Protein_Ontology_IDs', 'Genomic_Type'])\n",
     "\n",
-    "print('There are {edge_count} string-protein ontology edges'.format(edge_count=len(stpr_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 47,
-   "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>STRING_IDs</th>\n",
-       "      <th>Protein_Ontology_IDs</th>\n",
-       "      <th>Genomic_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ENSP00000000233</td>\n",
-       "      <td>PR_P84085</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>ENSP00000000233</td>\n",
-       "      <td>PR_000004204</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>ENSP00000000412</td>\n",
-       "      <td>PR_P20645</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>ENSP00000000412</td>\n",
-       "      <td>PR_000010032</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>ENSP00000000442</td>\n",
-       "      <td>PR_P11474-1</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "        STRING_IDs Protein_Ontology_IDs Genomic_Type\n",
-       "0  ENSP00000000233            PR_P84085         None\n",
-       "1  ENSP00000000233         PR_000004204         None\n",
-       "2  ENSP00000000412            PR_P20645         None\n",
-       "3  ENSP00000000412         PR_000010032         None\n",
-       "4  ENSP00000000442          PR_P11474-1         None"
-      ]
-     },
-     "execution_count": 47,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} string-protein ontology edges'.format(edge_count=len(stpr_data.drop_duplicates())))\n",
     "stpr_data.head(n=5)"
    ]
   },
@@ -4256,22 +1970,14 @@
     "- protein-catalyst \n",
     "- protein-pathway\n",
     "\n",
-    "**Output:** [`UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt`](https://www.dropbox.com/s/wvk1yv28xb06mfr/UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt?dl=1)"
+    "**Output:** `UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt`"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 48,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 1177914/1177914 [01:38<00:00, 11986.26it/s] \n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "genomic_id_mapper(reformatted_mapped_identifiers,\n",
     "                  processed_data_location + 'UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt',\n",
@@ -4279,116 +1985,18 @@
     "                  None, False, True)"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
   {
    "cell_type": "code",
-   "execution_count": 49,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 601872 uniprot accession-protein ontology edges\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# load data, print the number of rows, and preview it\n",
     "uapr_data = pandas.read_csv(processed_data_location + 'UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Uniprot_Accession_IDs', 'Protein_Ontology_IDs', 'Genomic_Type'],\n",
-    "                            delimiter='\\t')\n",
+    "                            header=None, delimiter='\\t', low_memory=False,\n",
+    "                            names=['Uniprot_Accession_IDs', 'Protein_Ontology_IDs', 'Genomic_Type'])\n",
     "\n",
-    "print('There are {edge_count} uniprot accession-protein ontology edges'.format(edge_count=len(uapr_data.drop_duplicates())))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 50,
-   "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>Uniprot_Accession_IDs</th>\n",
-       "      <th>Protein_Ontology_IDs</th>\n",
-       "      <th>Genomic_Type</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>A0A023HJ61</td>\n",
-       "      <td>PR_P20338</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>A0A023HJ61</td>\n",
-       "      <td>PR_000013631</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>A0A023IP86</td>\n",
-       "      <td>PR_000009129</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>A0A023IP86</td>\n",
-       "      <td>PR_P13612-2</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>A0A023IP86</td>\n",
-       "      <td>PR_P13612</td>\n",
-       "      <td>None</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Uniprot_Accession_IDs Protein_Ontology_IDs Genomic_Type\n",
-       "0            A0A023HJ61            PR_P20338         None\n",
-       "1            A0A023HJ61         PR_000013631         None\n",
-       "2            A0A023IP86         PR_000009129         None\n",
-       "3            A0A023IP86          PR_P13612-2         None\n",
-       "4            A0A023IP86            PR_P13612         None"
-      ]
-     },
-     "execution_count": 50,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} uniprot accession-protein ontology edges'.format(edge_count=len(uapr_data.drop_duplicates())))\n",
     "uapr_data.head(n=5)"
    ]
   },
@@ -4416,7 +2024,6 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "***\n",
     "### ChEBI-MeSH Identifiers <a class=\"anchor\" id=\"mesh-chebi\"></a>\n",
     "\n",
     "**Data Source Wiki Page:** [mapping-mesh-to-chebi](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#mapping-mesh-identifiers-to-chebi-identifiers)  \n",
@@ -4425,135 +2032,179 @@
     "- chemical-gene  \n",
     "- chemical-disease\n",
     "\n",
-    "**Dependencies:** This script assumes that the [`ncbo_rest_api.py`](https://gist.github.com/callahantiff/a28fb3160782f42f104e9ec41553af0d) script was run and the data generated from this file was written to `./resources/processed_data/temp`. \n",
+    "**Dependencies:** Recapitulates the [`LOOM`](https://www.bioontology.org/wiki/BioPortal_Mappings) algorithm implemented by BioPortal when creating mappings between resources. The procedure is relatively straightforward and consists of the following:\n",
+    "- For all MeSH `SCR Chemicals`, obtain the following information:  \n",
+    "  - <u>Identifiers</u>: MeSH identifiers     \n",
+    "  - <u>Labels</u>: string labels using the `RDFS:label` object property  \n",
+    "  - <u>Synonyms</u>: track down all synonyms using the `vocab:concept` and `vocab:preferredConcept` object properties   \n",
+    "- For all ChEBI classes, obtain the following information:  \n",
+    "  - <u>Labels</u>: string labels using the `RDFS:label` object property  \n",
+    "  - <u>Synonyms</u>: track down all synonyms using all `synonym` object properties \n",
+    "  \n",
+    "*Alternatively:* You can use the [`ncbo_rest_api.py`](https://gist.github.com/callahantiff/a28fb3160782f42f104e9ec41553af0d) script to pull mappings from the BioPortal API, but note that it takes >2 days for it to finish.\n",
     "\n",
-    "**Output:** [`CHEBI_MESH_MAP.txt`](https://www.dropbox.com/s/1mbd6a12dmjslae/MESH_CHEBI_MAP.txt?dl=1)\n"
+    "**Output:** `CHEBI_MESH_MAP.txt`\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***  \n",
+    "**MeSH**  \n",
+    "Downloads the `nt`-formatted version of the current MeSH vocabulary. Preprocesing is then performed in order to reformat the data so that it can be converted into a Pandas DataFrame in preparation of merging it with `ChEBI` in order to identify overlapping concepts."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# download data\n",
+    "url = 'ftp://nlmpubs.nlm.nih.gov/online/mesh/rdf/2021/mesh2021.nt'\n",
+    "if not os.path.exists(unprocessed_data_location + 'mesh2021.nt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "    \n",
+    "# load data\n",
+    "mesh = [x.split('> ') for x in tqdm(open(unprocessed_data_location + 'mesh2021.nt').readlines())]"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 51,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Data from https://www.dropbox.com/s/1mbd6a12dmjslae/MESH_CHEBI_MAP.txt?dl=1\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "# download existing build version\n",
-    "url = 'https://www.dropbox.com/s/1mbd6a12dmjslae/CHEBI_MESH_MAP.txt?dl=1'\n",
-    "data_downloader(url, processed_data_location, 'CHEBI_MESH_MAP.txt')"
+    "# preprocess data\n",
+    "mesh_dict, results = {}, []\n",
+    "for row in tqdm(mesh):\n",
+    "    dbx, lab, msh_type = None, None, None\n",
+    "    s, p, o = row[0].split('/')[-1], row[1].split('#')[-1], row[2]  \n",
+    "    if s[0] in ['C', 'D'] and ('.' not in s and 'Q' not in s) and len(s) >= 5:\n",
+    "        s = 'MESH_' + s\n",
+    "        if p == 'preferredConcept' or p == 'concept': dbx = 'MESH_' + o.split('/')[-1]\n",
+    "        if 'label' in p.lower(): lab = o.split('\"')[1]\n",
+    "        if 'type' in p.lower(): msh_type = o.split('#')[1]\n",
+    "        if s in mesh_dict.keys():\n",
+    "            if dbx is not None: mesh_dict[s]['dbxref'].add(dbx)\n",
+    "            if lab is not None: mesh_dict[s]['label'].add(lab)\n",
+    "            if msh_type is not None: mesh_dict[s]['type'].add(msh_type)\n",
+    "        else:\n",
+    "            mesh_dict[s] = {'dbxref': set() if dbx is None else {dbx},\n",
+    "                            'label': set() if lab is None else {lab},\n",
+    "                            'type': set() if msh_type is None else {msh_type},\n",
+    "                            'synonym': set()}\n",
+    "\n",
+    "# fine tune dictionary - obtain labels for each entry's synonym identifiers\n",
+    "for key in tqdm(mesh_dict.keys()):\n",
+    "    for i in mesh_dict[key]['dbxref']:\n",
+    "        if len(mesh_dict[key]['dbxref']) > 0 and i in mesh_dict.keys():\n",
+    "            mesh_dict[key]['synonym'] |= mesh_dict[i]['label']\n",
+    "\n",
+    "# expand data and convert to pandas DataFrame\n",
+    "for key, value in tqdm(mesh_dict.items()):\n",
+    "    results += [[key, list(value['label'])[0], 'NAME']]\n",
+    "    if len(value['synonym']) > 0:\n",
+    "        for i in value['synonym']:\n",
+    "            results += [[key, i, 'SYNONYM']]\n",
+    "mesh_filtered = pandas.DataFrame({'CODE': [x[0] for x in results],\n",
+    "                                  'TYPE': [x[2] for x in results],\n",
+    "                                  'STRING': [x[1] for x in results]})\n",
+    "\n",
+    "# lowercase all strings and remove white space and punctuation\n",
+    "mesh_filtered['STRING'] = mesh_filtered['STRING'].str.lower()\n",
+    "mesh_filtered['STRING'] = mesh_filtered['STRING'].str.replace('[^\\w]','')\n",
+    "\n",
+    "# preview data\n",
+    "mesh_filtered.head()"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Preview Processed Data_"
+    "***  \n",
+    "**ChEBI**  \n",
+    "Downloads the flat-file containing labels and synonyms for all classes in the `ChEBI` ontology. Preprocessing is then performed in order to reformat the data so that it can be converted into a Pandas DataFrame in preparation of merging it with `MeSH` in order to identify overlapping concepts."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# download data\n",
+    "url = 'ftp://ftp.ebi.ac.uk/pub/databases/chebi/Flat_file_tab_delimited/names.tsv.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'names.tsv'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "    \n",
+    "# load data\n",
+    "chebi = pandas.read_csv(unprocessed_data_location + 'names.tsv', header=0, delimiter='\\t')\n",
+    "\n",
+    "# preprocess data\n",
+    "chebi_filtered = chebi[['COMPOUND_ID', 'TYPE', 'NAME']]\n",
+    "chebi_filtered.drop_duplicates(subset=None, keep='first', inplace=True)\n",
+    "chebi_filtered.columns = ['CODE', 'TYPE', 'STRING']\n",
+    "\n",
+    "# append CHEBI to the number in each code\n",
+    "chebi_filtered['CODE'] = chebi_filtered['CODE'].apply(lambda x: \"{}{}\".format('CHEBI_', x))\n",
+    "\n",
+    "# lowercase all strings and remove white space and punctuation\n",
+    "chebi_filtered['STRING'] = chebi_filtered['STRING'].str.lower()\n",
+    "chebi_filtered['STRING'] = chebi_filtered['STRING'].str.replace('[^\\w]','')\n",
+    "\n",
+    "# preview data\n",
+    "chebi_filtered.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***  \n",
+    "**Merge Identifier Data**  \n",
+    "Performs an inner merge of the `MeSH` and `ChEBI` Pandas DataFrames in order to find concepts that exist in both DataFrames. Results are then written out to a text file."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 52,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 11434 MeSH-ChEBI edges\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "mc_data = pandas.read_csv(processed_data_location + 'CHEBI_MESH_MAP.txt',\n",
-    "                          delimiter='\\t',\n",
-    "                          header=None,\n",
-    "                          names=['ChEBI_IDs', 'MeSH_IDs'])\n",
+    "# merge data\n",
+    "chem_merge = pandas.merge(chebi_filtered[['STRING', 'CODE']], mesh_filtered[['STRING', 'CODE']], on='STRING', how='inner')\n",
     "\n",
-    "print('There are {edge_count} ChEBI-MESH edges'.format(edge_count=len(mc_data)))"
+    "# filter results\n",
+    "mesh_edges = set()\n",
+    "for idx, row in chem_merge.drop_duplicates().iterrows():\n",
+    "    mesh, chebi = row['CODE_y'], row['CODE_x']\n",
+    "    syns = [x for x in mesh_dict[mesh]['dbxref'] if 'C' in x or 'D' in x]\n",
+    "    mesh_edges.add(tuple([mesh, chebi]))\n",
+    "    if len(syns) > 0:\n",
+    "        for x in syns:\n",
+    "            mesh_edges.add(tuple([x, chebi]))\n",
+    "\n",
+    "# write resulting mappings\n",
+    "with open(processed_data_location + 'MESH_CHEBI_MAP.txt', 'w') as out:\n",
+    "    for pair in mesh_edges:\n",
+    "        out.write(pair[0] + '\\t' + pair[1] + '\\n')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
-   "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>MeSH_IDs</th>\n",
-       "      <th>ChEBI_IDs</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>MESH_C535085</td>\n",
-       "      <td>CHEBI_133814</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>MESH_C008574</td>\n",
-       "      <td>CHEBI_17221</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>MESH_C492482</td>\n",
-       "      <td>CHEBI_34581</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>MESH_C007556</td>\n",
-       "      <td>CHEBI_135978</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>MESH_C500395</td>\n",
-       "      <td>CHEBI_29138</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "       MeSH_IDs     ChEBI_IDs\n",
-       "0  MESH_C535085  CHEBI_133814\n",
-       "1  MESH_C008574   CHEBI_17221\n",
-       "2  MESH_C492482   CHEBI_34581\n",
-       "3  MESH_C007556  CHEBI_135978\n",
-       "4  MESH_C500395   CHEBI_29138"
-      ]
-     },
-     "execution_count": 53,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "mc_data.head(n=5)"
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# load data\n",
+    "data = pandas.read_csv(processed_data_location + 'MESH_CHEBI_MAP.txt', header=None, names=['MESH_ID', 'CHEBI_ID'], delimiter='\\t')\n",
+    "\n",
+    "# preview mapping results\n",
+    "print('There are {} MeSH-ChEBI Edges'.format(len(data)))\n",
+    "data.head(n=5)"
    ]
   },
   {
@@ -4566,112 +2217,112 @@
     "\n",
     "**Data Source Wiki Page:** [DisGeNET](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#disgenet)  \n",
     "\n",
-    "**Purpose:** This script downloads the [disease_mappings.tsv](https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz) to map UMLS identifiers to Human Disease and Human Phenotype identifiers when creating the following edges:  \n",
+    "**Purpose:** This script downloads the Human Phenotype Ontology (HPO), the MonDO Disease Ontology (MONDO), and [disease_mappings.tsv](https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz) in order to map UMLS identifiers to HPO and MONDO identifiers when creating the following edges:  \n",
     "- chemical-disease  \n",
-    "- disease-phenotype\n",
+    "- disease-phenotype  \n",
+    "- chemical-phenotype  \n",
+    "- gene-phenotype  \n",
+    "- variant-phenotype  \n",
     "\n",
     "**Output:**   \n",
-    "- Human Disease Ontology Mappings ➞ [`DISEASE_MONDO_MAP.txt`](https://www.dropbox.com/s/ziv0glx4ph9jidc/DISEASE_DOID_MAP.txt?dl=1)  \n",
-    "- Human Phenotype Ontology Mappings ➞ [`PHENOTYPE_HPO_MAP.txt`](https://www.dropbox.com/s/71ts7kw44vm70tg/PHENOTYPE_HPO_MAP.txt?dl=1)"
+    "- Human Disease Ontology Mappings ➞ `DISEASE_MONDO_MAP.txt`\n",
+    "- Human Phenotype Ontology Mappings ➞ `PHENOTYPE_HPO_MAP.txt`"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "**MONDO Identifiers**  \n",
+    "`MONDO` contains DbXRef mappings to other disease terminology identifiers. To make this useful, we will store the DbXRefs as a dictionary with `MONDO` identifiers as the values."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Gzipped Data from https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "url = 'https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'mondo_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/mondo.owl',\n",
+    "                             unprocessed_data_location + 'mondo_with_imports.owl'))\n",
+    "    \n",
+    "# read data into RDFLib graph object\n",
+    "mondo_graph = Graph().parse(unprocessed_data_location + 'mondo_with_imports.owl')\n",
+    "print('There are {} axioms in the ontology (date: {})'.format(len(mondo_graph), datetime.datetime.now().strftime('%m/%d/%Y')))\n",
+    "\n",
+    "# get dbxrefs for all MONDO classes\n",
+    "dbxref_res = gets_ontology_class_dbxrefs(mondo_graph)[0]\n",
+    "mondo_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')} for k, v in dbxref_res.items() if 'MONDO' in str(v)}\n",
+    "\n",
+    "# pickle dictionary\n",
+    "pickle.dump(mondo_dict, open(processed_data_location + 'Mondo_Identifier_Map.pkl', 'wb'), protocol=4)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "**HPO Identifiers**  \n",
+    "`HPO` contains DbXRef mappings to other disease terminology identifiers. To make this useful, we will store the DbXRefs as a dictionary with `HPO` identifiers as the values."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'hp_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/hp.owl',\n",
+    "                             unprocessed_data_location + 'hp_with_imports.owl'))\n",
+    "\n",
+    "# read data into RDFLib graph object\n",
+    "hp_graph = Graph().parse(unprocessed_data_location + 'hp_with_imports.owl')\n",
+    "print('There are {} axioms in the ontology (date: {})'.format(len(hp_graph), datetime.datetime.now().strftime('%m/%d/%Y')))\n",
+    "\n",
+    "# get dbxrefs for all HPO classes\n",
+    "dbxref_res = gets_ontology_class_dbxrefs(hp_graph)[0]\n",
+    "hp_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')} for k, v in dbxref_res.items() if 'HP' in str(v)}\n",
+    "\n",
+    "# pickle dictionary\n",
+    "pickle.dump(hp_dict, open(processed_data_location + 'HPO_Identifier_Map.pkl', 'wb'), protocol=4)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "**DisGeNET Disease Mappings**"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
-   "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>diseaseId</th>\n",
-       "      <th>name</th>\n",
-       "      <th>vocabulary</th>\n",
-       "      <th>code</th>\n",
-       "      <th>vocabularyName</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>C0018923</td>\n",
-       "      <td>Hemangiosarcoma</td>\n",
-       "      <td>DO</td>\n",
-       "      <td>0001816</td>\n",
-       "      <td>angiosarcoma</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>C0854893</td>\n",
-       "      <td>Angiosarcoma non-metastatic</td>\n",
-       "      <td>DO</td>\n",
-       "      <td>0001816</td>\n",
-       "      <td>angiosarcoma</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>C0033999</td>\n",
-       "      <td>Pterygium</td>\n",
-       "      <td>DO</td>\n",
-       "      <td>0002116</td>\n",
-       "      <td>pterygium</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  diseaseId                         name vocabulary     code vocabularyName\n",
-       "0  C0018923              Hemangiosarcoma         DO  0001816   angiosarcoma\n",
-       "1  C0854893  Angiosarcoma non-metastatic         DO  0001816   angiosarcoma\n",
-       "2  C0033999                    Pterygium         DO  0002116      pterygium"
-      ]
-     },
-     "execution_count": 55,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "disease_data = pandas.read_csv(unprocessed_data_location + 'disease_mappings.tsv',\n",
-    "                               header=0,\n",
-    "                               delimiter='\\t')  # DisGeNET change delimiter from \"|\" to \"\\t\" in May 2020\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# download data\n",
+    "url = 'https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'disease_mappings.tsv'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "    \n",
+    "# load data\n",
+    "disease_data = pandas.read_csv(unprocessed_data_location + 'disease_mappings.tsv', header=0, delimiter='\\t')\n",
     "\n",
+    "# reformat data\n",
+    "disease_data['vocabulary'] = disease_data['vocabulary'].str.lower()\n",
+    "disease_data['diseaseId'] = disease_data['diseaseId'].str.lower()\n",
+    "disease_data['vocabulary'] = ['doid' if x == 'do' else 'ordoid' if x == 'ordo' else x for x in disease_data['vocabulary']]\n",
+    "\n",
+    "# preview data\n",
     "disease_data.head(n=3)"
    ]
   },
@@ -4685,62 +2336,53 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 56,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 39267/39267 [00:08<00:00, 4706.20it/s]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "# get all CUIs found with HPO and MONDO\n",
-    "disease_data_keep = disease_data.query('vocabulary == \"HPO\" | vocabulary == \"MONDO\"')\n",
+    "disease_data_keep = disease_data.query('vocabulary == \"hpo\" | vocabulary == \"mondo\"')\n",
     "\n",
     "# create mondo and hpo dictionary\n",
     "hp_mondo_dict = {}\n",
     "for idx, row in tqdm(disease_data_keep.iterrows(), total=disease_data_keep.shape[0]):\n",
-    "    if row['vocabulary'] == 'MONDO': key, value = row['diseaseId'], row['vocabulary'] + ':' + row['code']\n",
-    "    else: key, value = row['diseaseId'], row['code']\n",
+    "    if row['vocabulary'] == 'mondo': key, value = 'umls:' + row['diseaseId'], 'MONDO:' + row['code']\n",
+    "    else: key, value = 'umls:' + row['diseaseId'], row['code']\n",
     "    if key in hp_mondo_dict.keys(): hp_mondo_dict[key] |= {value}\n",
-    "    else: hp_mondo_dict[key] = {value}\n"
+    "    else: hp_mondo_dict[key] = {value}\n",
+    "# add ontology mappings from MONDO and HPO\n",
+    "for key in tqdm(hp_mondo_dict.keys()):\n",
+    "    if key in mondo_dict.keys():\n",
+    "        hp_mondo_dict[key] = set(list(hp_mondo_dict[key]) + list(mondo_dict[key]))\n",
+    "    if key in hp_dict.keys():\n",
+    "        hp_mondo_dict[key] = set(list(hp_mondo_dict[key]) + list(hp_dict[key]))"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 208924/208924 [00:48<00:00, 4299.61it/s]\n"
-     ]
-    }
-   ],
-   "source": [
-    "# get all rows for HPO/MONDO CUIs\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# get all rows for HPO/MONDO CUIs to obtain mappings to other disease identifiers\n",
     "disease_data_other = disease_data[disease_data.diseaseId.isin(disease_data_keep['diseaseId'])]\n",
     "\n",
     "# get all other codes that map to MONDO or HPO by hopping through MONDO/HPO relevant CUIs\n",
     "disease_dict = {}\n",
     "for idx, row in tqdm(disease_data_other.iterrows(), total=disease_data_other.shape[0]):\n",
-    "    if row['vocabulary'] == 'MONDO' or row['vocabulary'] == 'HPO':\n",
-    "        key, value = row['diseaseId'], row['code']\n",
+    "    if row['vocabulary'] == 'mondo' or row['vocabulary'] == 'hpo':\n",
+    "        key, value = 'umls:' + row['diseaseId'].lower(), row['code']\n",
     "        if key in disease_dict.keys(): disease_dict[key] |= {value}\n",
     "        else: disease_dict[key] = {value}\n",
     "    else:\n",
-    "        if 'MONDO' not in row['code'] or 'HP' not in row['code']:\n",
-    "            if ':' not in row['code']:\n",
-    "                key, value = row['vocabulary'] + ':' + row['code'], hp_mondo_dict[row['diseaseId']]\n",
-    "            else:\n",
-    "                key, value = row['code'], hp_mondo_dict[row['diseaseId']]\n",
+    "        if 'mondo' not in row['code'] or 'hp' not in row['code']:\n",
+    "            if ':' not in row['code']: key, value = row['vocabulary'] + ':' + row['code'], hp_mondo_dict['umls:' + row['diseaseId']]\n",
+    "            else: key, value = row['code'], hp_mondo_dict['umls:' + row['diseaseId']]\n",
     "            if key in disease_dict.keys(): disease_dict[key] |= value\n",
-    "            else: disease_dict[key] = value"
+    "            else: disease_dict[key] = value\n",
+    "\n",
+    "# add ontology dictionaries\n",
+    "disease_dict = {**disease_dict, **mondo_dict, **hp_dict}"
    ]
   },
   {
@@ -4752,31 +2394,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 61276/61276 [00:00<00:00, 136287.37it/s]\n"
-     ]
-    }
-   ],
-   "source": [
-    "with open(processed_data_location + 'DISEASE_MONDO_MAP.txt', 'w') as outfile1,open(processed_data_location + 'PHENOTYPE_HPO_MAP.txt', 'w') as outfile2:\n",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "with open(processed_data_location + 'DISEASE_MONDO_MAP.txt', 'w') as outfile1, open(processed_data_location + 'PHENOTYPE_HPO_MAP.txt', 'w') as outfile2:\n",
     "    for k, v in tqdm(disease_dict.items()):\n",
-    "        # output MONDO mappings\n",
     "        if any(x for x in v if x.startswith('MONDO')):\n",
     "            for idx in [x.replace(':', '_') for x in v if 'MONDO' in x]:\n",
-    "                outfile1.write(k.replace('DO', 'DOID') + '\\t' + idx + '\\n')\n",
-    "        # output HP mappings\n",
+    "                outfile1.write(k.upper() + '\\t' + idx + '\\n')\n",
     "        if any(x for x in v if x.startswith('HP')):\n",
     "            for idx in [x.replace(':', '_') for x in v if 'HP' in x]:\n",
-    "                outfile2.write(k.replace('DO', 'DOID') + '\\t' + idx + '\\n')\n",
-    "\n",
-    "outfile1.close()\n",
-    "outfile2.close()"
+    "                outfile2.write(k.upper() + '\\t' + idx + '\\n')"
    ]
   },
   {
@@ -4788,101 +2417,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 190118 disease-DOID edges\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "dis_data = pandas.read_csv(processed_data_location + 'DISEASE_MONDO_MAP.txt',\n",
-    "                           header=None,\n",
-    "                           names=['Disease_IDs', 'MONDO_IDs'],\n",
-    "                           delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "dis_data = pandas.read_csv(processed_data_location + 'DISEASE_MONDO_MAP.txt', header=None, names=['Disease_IDs', 'MONDO_IDs'], delimiter='\\t')\n",
     "\n",
-    "print('There are {} disease-DOID edges'.format(len(dis_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 60,
-   "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>Disease_IDs</th>\n",
-       "      <th>DOID_IDs</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>DOID:0001816</td>\n",
-       "      <td>MONDO_0016982</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>DOID:0002116</td>\n",
-       "      <td>MONDO_0005085</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>DOID:0014667</td>\n",
-       "      <td>MONDO_0019052</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>DOID:0014667</td>\n",
-       "      <td>MONDO_0005066</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>DOID:0040084</td>\n",
-       "      <td>MONDO_0005972</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "    Disease_IDs       DOID_IDs\n",
-       "0  DOID:0001816  MONDO_0016982\n",
-       "1  DOID:0002116  MONDO_0005085\n",
-       "2  DOID:0014667  MONDO_0019052\n",
-       "3  DOID:0014667  MONDO_0005066\n",
-       "4  DOID:0040084  MONDO_0005972"
-      ]
-     },
-     "execution_count": 60,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {} disease-MONDO edges'.format(len(dis_data)))\n",
     "dis_data.head(n=5)"
    ]
   },
@@ -4895,101 +2437,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 61,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 32138 phenotype-HPO edges\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "hp_data = pandas.read_csv(processed_data_location + 'PHENOTYPE_HPO_MAP.txt',\n",
-    "                          header=None,\n",
-    "                          names=['Disease_IDs', 'HP_IDs'],\n",
-    "                          delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "hp_data = pandas.read_csv(processed_data_location + 'PHENOTYPE_HPO_MAP.txt', header=None, names=['Disease_IDs', 'HP_IDs'], delimiter='\\t')\n",
     "\n",
-    "print('There are {} phenotype-HPO edges'.format(len(hp_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 62,
-   "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>Disease_IDs</th>\n",
-       "      <th>HP_IDs</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>DOID:0001816</td>\n",
-       "      <td>HP_0200058</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>DOID:0002116</td>\n",
-       "      <td>HP_0001059</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>DOID:0040099</td>\n",
-       "      <td>HP_0011276</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>DOID:0050127</td>\n",
-       "      <td>HP_0000246</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>DOID:0050144</td>\n",
-       "      <td>HP_0012265</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "    Disease_IDs      HP_IDs\n",
-       "0  DOID:0001816  HP_0200058\n",
-       "1  DOID:0002116  HP_0001059\n",
-       "2  DOID:0040099  HP_0011276\n",
-       "3  DOID:0050127  HP_0000246\n",
-       "4  DOID:0050144  HP_0012265"
-      ]
-     },
-     "execution_count": 62,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {} phenotype-HPO edges'.format(len(hp_data)))\n",
     "hp_data.head(n=5)"
    ]
   },
@@ -4997,1524 +2452,53 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
+    "### Human Protein Atlas/GTEx Tissue/Cells - UBERON + Cell Ontology + Cell Line Ontology <a class=\"anchor\" id=\"hpa-uberon\"></a>\n",
+    "\n",
+    "**Data Source Wiki Page:**  \n",
+    "- [human-protein-atlas](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources/#human-protein-atlas) \n",
+    "- [genotype-tissue-expression-project](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#the-genotype-tissue-expression-gtex-project)  \n",
+    "\n",
     "<br>\n",
     "\n",
-    "**Convert DOID Identifiers Into MONDO Identifiers**  \n",
-    "`MONDO` contains DbXRef mappings to `DOID` identifiers. In order to convert the mappings created above into MONDO identifiers. To do this, we need to read in the `MONDO` ontology and obtain the DbXRefs. To make this useful, we will store the DbXRefs as a dictionary with `MONDO` identifiers as the keys."
+    "**Purpose:** Downloads a query for cell, tissue, and blood types with overexpressed protein-coding genes in the human proteome ([`proteinatlas_search.tsv`](https://www.proteinatlas.org/api/search_download.php?search=&columns=g,eg,up,pe,rnatsm,rnaclsm,rnacasm,rnabrsm,rnabcsm,rnablsm,scl,t_RNA_adipose_tissue,t_RNA_adrenal_gland,t_RNA_amygdala,t_RNA_appendix,t_RNA_basal_ganglia,t_RNA_bone_marrow,t_RNA_breast,t_RNA_cerebellum,t_RNA_cerebral_cortex,t_RNA_cervix,_uterine,t_RNA_colon,t_RNA_corpus_callosum,t_RNA_ductus_deferens,t_RNA_duodenum,t_RNA_endometrium_1,t_RNA_epididymis,t_RNA_esophagus,t_RNA_fallopian_tube,t_RNA_gallbladder,t_RNA_heart_muscle,t_RNA_hippocampal_formation,t_RNA_hypothalamus,t_RNA_kidney,t_RNA_liver,t_RNA_lung,t_RNA_lymph_node,t_RNA_midbrain,t_RNA_olfactory_region,t_RNA_ovary,t_RNA_pancreas,t_RNA_parathyroid_gland,t_RNA_pituitary_gland,t_RNA_placenta,t_RNA_pons_and_medulla,t_RNA_prostate,t_RNA_rectum,t_RNA_retina,t_RNA_salivary_gland,t_RNA_seminal_vesicle,t_RNA_skeletal_muscle,t_RNA_skin_1,t_RNA_small_intestine,t_RNA_smooth_muscle,t_RNA_spinal_cord,t_RNA_spleen,t_RNA_stomach_1,t_RNA_testis,t_RNA_thalamus,t_RNA_thymus,t_RNA_thyroid_gland,t_RNA_tongue,t_RNA_tonsil,t_RNA_urinary_bladder,t_RNA_vagina,t_RNA_B-cells,t_RNA_dendritic_cells,t_RNA_granulocytes,t_RNA_monocytes,t_RNA_NK-cells,t_RNA_T-cells,t_RNA_total_PBMC,cell_RNA_A-431,cell_RNA_A549,cell_RNA_AF22,cell_RNA_AN3-CA,cell_RNA_ASC_diff,cell_RNA_ASC_TERT1,cell_RNA_BEWO,cell_RNA_BJ,cell_RNA_BJ_hTERT+,cell_RNA_BJ_hTERT+_SV40_Large_T+,cell_RNA_BJ_hTERT+_SV40_Large_T+_RasG12V,cell_RNA_CACO-2,cell_RNA_CAPAN-2,cell_RNA_Daudi,cell_RNA_EFO-21,cell_RNA_fHDF/TERT166,cell_RNA_HaCaT,cell_RNA_HAP1,cell_RNA_HBEC3-KT,cell_RNA_HBF_TERT88,cell_RNA_HDLM-2,cell_RNA_HEK_293,cell_RNA_HEL,cell_RNA_HeLa,cell_RNA_Hep_G2,cell_RNA_HHSteC,cell_RNA_HL-60,cell_RNA_HMC-1,cell_RNA_HSkMC,cell_RNA_hTCEpi,cell_RNA_hTEC/SVTERT24-B,cell_RNA_hTERT-HME1,cell_RNA_HUVEC_TERT2,cell_RNA_K-562,cell_RNA_Karpas-707,cell_RNA_LHCN-M2,cell_RNA_MCF7,cell_RNA_MOLT-4,cell_RNA_NB-4,cell_RNA_NTERA-2,cell_RNA_PC-3,cell_RNA_REH,cell_RNA_RH-30,cell_RNA_RPMI-8226,cell_RNA_RPTEC_TERT1,cell_RNA_RT4,cell_RNA_SCLC-21H,cell_RNA_SH-SY5Y,cell_RNA_SiHa,cell_RNA_SK-BR-3,cell_RNA_SK-MEL-30,cell_RNA_T-47d,cell_RNA_THP-1,cell_RNA_TIME,cell_RNA_U-138_MG,cell_RNA_U-2_OS,cell_RNA_U-2197,cell_RNA_U-251_MG,cell_RNA_U-266/70,cell_RNA_U-266/84,cell_RNA_U-698,cell_RNA_U-87_MG,cell_RNA_U-937,cell_RNA_WM-115,blood_RNA_basophil,blood_RNA_classical_monocyte,blood_RNA_eosinophil,blood_RNA_gdT-cell,blood_RNA_intermediate_monocyte,blood_RNA_MAIT_T-cell,blood_RNA_memory_B-cell,blood_RNA_memory_CD4_T-cell,blood_RNA_memory_CD8_T-cell,blood_RNA_myeloid_DC,blood_RNA_naive_B-cell,blood_RNA_naive_CD4_T-cell,blood_RNA_naive_CD8_T-cell,blood_RNA_neutrophil,blood_RNA_NK-cell,blood_RNA_non-classical_monocyte,blood_RNA_plasmacytoid_DC,blood_RNA_T-reg,blood_RNA_total_PBMC,brain_RNA_amygdala,brain_RNA_basal_ganglia,brain_RNA_cerebellum,brain_RNA_cerebral_cortex,brain_RNA_hippocampal_formation,brain_RNA_hypothalamus,brain_RNA_midbrain,brain_RNA_olfactory_region,brain_RNA_pons_and_medulla,brain_RNA_thalamus&format=tsv)) via [API](https://www.proteinatlas.org/about/help/dataaccess) and median gene-level TPM by tissue for all genes that are not protein-coding ([`GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct`](https://storage.googleapis.com/gtex_analysis_v8/rna_seq_data/GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct.gz)) in order to create mappings between cell and tissue type strings to the Uber-Anatomy, Cell Ontology, and Cell Line Ontology concepts (see [human-protein-atlas](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#human-protein-atlas) for details on the mapping process). The mappings are then used to create the following edge types:  \n",
+    "- rna-cell line  \n",
+    "- rna-tissue type   \n",
+    "- protein-cell line  \n",
+    "- protein-tissue type  \n",
+    "\n",
+    "\n",
+    "**Output:**  \n",
+    "- All HPA tissue and cell type strings ➞ `HPA_tissues.txt`  \n",
+    "- Mapping HPA strings to ontology concepts (documentation) ➞ `zooma_tissue_cell_mapping_04JAN2020.xlsx` \n",
+    "- Final HPA-ontology mappings ➞ `HPA_GTEx_TISSUE_CELL_MAP.txt`\n",
+    "- HPA Edges ➞ `HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt`"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": 63,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "0"
-      ]
-     },
-     "execution_count": 63,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# download ontology using subprocess and OWLTOOLS in order to get the ontology and its imported ontologies\n",
-    "subprocess.check_call(['./pkt_kg/libs/owltools',\n",
-    "                       'http://purl.obolibrary.org/obo/mondo.owl',\n",
-    "                       '--merge-import-closure',\n",
-    "                       '-o',\n",
-    "                       unprocessed_data_location + 'mondo_with_imports.owl'])"
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "***\n",
+    "**Human Protein Atlas**  \n",
+    "To expedite the mapping process, all HPA tissues, cells, cell lines, and fluid types are extracted from the HPA data columns."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 65,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 2348407 axioms in the ontology (date: 12/03/2020)\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "mondo_graph = Graph()\n",
-    "mondo_graph.parse(unprocessed_data_location + 'mondo_with_imports.owl')\n",
+    "# download data\n",
+    "url = 'https://www.proteinatlas.org/api/search_download.php?search=&columns=g,eg,up,pe,rnatsm,rnaclsm,rnacasm,rnabrsm,rnabcsm,rnablsm,scl,t_RNA_adipose_tissue,t_RNA_adrenal_gland,t_RNA_amygdala,t_RNA_appendix,t_RNA_basal_ganglia,t_RNA_bone_marrow,t_RNA_breast,t_RNA_cerebellum,t_RNA_cerebral_cortex,t_RNA_cervix,_uterine,t_RNA_colon,t_RNA_corpus_callosum,t_RNA_ductus_deferens,t_RNA_duodenum,t_RNA_endometrium_1,t_RNA_epididymis,t_RNA_esophagus,t_RNA_fallopian_tube,t_RNA_gallbladder,t_RNA_heart_muscle,t_RNA_hippocampal_formation,t_RNA_hypothalamus,t_RNA_kidney,t_RNA_liver,t_RNA_lung,t_RNA_lymph_node,t_RNA_midbrain,t_RNA_olfactory_region,t_RNA_ovary,t_RNA_pancreas,t_RNA_parathyroid_gland,t_RNA_pituitary_gland,t_RNA_placenta,t_RNA_pons_and_medulla,t_RNA_prostate,t_RNA_rectum,t_RNA_retina,t_RNA_salivary_gland,t_RNA_seminal_vesicle,t_RNA_skeletal_muscle,t_RNA_skin_1,t_RNA_small_intestine,t_RNA_smooth_muscle,t_RNA_spinal_cord,t_RNA_spleen,t_RNA_stomach_1,t_RNA_testis,t_RNA_thalamus,t_RNA_thymus,t_RNA_thyroid_gland,t_RNA_tongue,t_RNA_tonsil,t_RNA_urinary_bladder,t_RNA_vagina,t_RNA_B-cells,t_RNA_dendritic_cells,t_RNA_granulocytes,t_RNA_monocytes,t_RNA_NK-cells,t_RNA_T-cells,t_RNA_total_PBMC,cell_RNA_A-431,cell_RNA_A549,cell_RNA_AF22,cell_RNA_AN3-CA,cell_RNA_ASC_diff,cell_RNA_ASC_TERT1,cell_RNA_BEWO,cell_RNA_BJ,cell_RNA_BJ_hTERT+,cell_RNA_BJ_hTERT+_SV40_Large_T+,cell_RNA_BJ_hTERT+_SV40_Large_T+_RasG12V,cell_RNA_CACO-2,cell_RNA_CAPAN-2,cell_RNA_Daudi,cell_RNA_EFO-21,cell_RNA_fHDF/TERT166,cell_RNA_HaCaT,cell_RNA_HAP1,cell_RNA_HBEC3-KT,cell_RNA_HBF_TERT88,cell_RNA_HDLM-2,cell_RNA_HEK_293,cell_RNA_HEL,cell_RNA_HeLa,cell_RNA_Hep_G2,cell_RNA_HHSteC,cell_RNA_HL-60,cell_RNA_HMC-1,cell_RNA_HSkMC,cell_RNA_hTCEpi,cell_RNA_hTEC/SVTERT24-B,cell_RNA_hTERT-HME1,cell_RNA_HUVEC_TERT2,cell_RNA_K-562,cell_RNA_Karpas-707,cell_RNA_LHCN-M2,cell_RNA_MCF7,cell_RNA_MOLT-4,cell_RNA_NB-4,cell_RNA_NTERA-2,cell_RNA_PC-3,cell_RNA_REH,cell_RNA_RH-30,cell_RNA_RPMI-8226,cell_RNA_RPTEC_TERT1,cell_RNA_RT4,cell_RNA_SCLC-21H,cell_RNA_SH-SY5Y,cell_RNA_SiHa,cell_RNA_SK-BR-3,cell_RNA_SK-MEL-30,cell_RNA_T-47d,cell_RNA_THP-1,cell_RNA_TIME,cell_RNA_U-138_MG,cell_RNA_U-2_OS,cell_RNA_U-2197,cell_RNA_U-251_MG,cell_RNA_U-266/70,cell_RNA_U-266/84,cell_RNA_U-698,cell_RNA_U-87_MG,cell_RNA_U-937,cell_RNA_WM-115,blood_RNA_basophil,blood_RNA_classical_monocyte,blood_RNA_eosinophil,blood_RNA_gdT-cell,blood_RNA_intermediate_monocyte,blood_RNA_MAIT_T-cell,blood_RNA_memory_B-cell,blood_RNA_memory_CD4_T-cell,blood_RNA_memory_CD8_T-cell,blood_RNA_myeloid_DC,blood_RNA_naive_B-cell,blood_RNA_naive_CD4_T-cell,blood_RNA_naive_CD8_T-cell,blood_RNA_neutrophil,blood_RNA_NK-cell,blood_RNA_non-classical_monocyte,blood_RNA_plasmacytoid_DC,blood_RNA_T-reg,blood_RNA_total_PBMC,brain_RNA_amygdala,brain_RNA_basal_ganglia,brain_RNA_cerebellum,brain_RNA_cerebral_cortex,brain_RNA_hippocampal_formation,brain_RNA_hypothalamus,brain_RNA_midbrain,brain_RNA_olfactory_region,brain_RNA_pons_and_medulla,brain_RNA_thalamus&format=tsv'\n",
+    "if not os.path.exists(unprocessed_data_location + 'proteinatlas_search.tsv'):\n",
+    "    data_downloader(url, unprocessed_data_location, 'proteinatlas_search.tsv.gz')\n",
     "\n",
-    "print('There are {} axioms in the ontology (date: {})'.format(len(mondo_graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 85,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "  0%|          | 0/2348407 [00:00<?, ?it/s]\u001b[A\n",
-      "  0%|          | 1/2348407 [00:00<138:46:51,  4.70it/s]\u001b[A\n",
-      "  0%|          | 8106/2348407 [00:00<96:49:08,  6.71it/s]\u001b[A\n",
-      "  1%|          | 20638/2348407 [00:00<67:24:43,  9.59it/s]\u001b[A\n",
-      "  2%|▏         | 35351/2348407 [00:00<46:53:29, 13.70it/s]\u001b[A\n",
-      "  2%|▏         | 50020/2348407 [00:00<32:37:01, 19.57it/s]\u001b[A\n",
-      "  3%|▎         | 64072/2348407 [00:00<22:41:37, 27.96it/s]\u001b[A\n",
-      "  3%|▎         | 75002/2348407 [00:01<15:48:41, 39.94it/s]\u001b[A\n",
-      "  4%|▍         | 89684/2348407 [00:01<10:59:52, 57.05it/s]\u001b[A\n",
-      "  4%|▍         | 102300/2348407 [00:01<7:39:24, 81.48it/s]\u001b[A\n",
-      "  5%|▍         | 114484/2348407 [00:01<5:20:11, 116.28it/s]\u001b[A\n",
-      "  5%|▌         | 127175/2348407 [00:01<3:42:56, 166.05it/s]\u001b[A\n",
-      "  6%|▌         | 141963/2348407 [00:01<2:35:05, 237.11it/s]\u001b[A\n",
-      "  7%|▋         | 156587/2348407 [00:01<1:47:55, 338.49it/s]\u001b[A\n",
-      "  7%|▋         | 169211/2348407 [00:02<1:15:12, 482.98it/s]\u001b[A\n",
-      "  8%|▊         | 182673/2348407 [00:02<52:23, 688.91it/s]  \u001b[A\n",
-      "  8%|▊         | 196916/2348407 [00:02<36:30, 982.11it/s]\u001b[A\n",
-      "  9%|▉         | 211768/2348407 [00:02<25:27, 1399.06it/s]\u001b[A\n",
-      " 10%|▉         | 225754/2348407 [00:02<17:46, 1990.12it/s]\u001b[A\n",
-      " 10%|█         | 241218/2348407 [00:02<12:25, 2827.43it/s]\u001b[A\n",
-      " 11%|█         | 256645/2348407 [00:02<08:41, 4007.71it/s]\u001b[A\n",
-      " 12%|█▏        | 272131/2348407 [00:02<06:06, 5662.49it/s]\u001b[A\n",
-      " 12%|█▏        | 287557/2348407 [00:02<04:18, 7963.98it/s]\u001b[A\n",
-      " 13%|█▎        | 302607/2348407 [00:02<03:03, 11123.55it/s]\u001b[A\n",
-      " 14%|█▎        | 317634/2348407 [00:03<02:12, 15361.99it/s]\u001b[A\n",
-      " 14%|█▍        | 332537/2348407 [00:03<01:35, 21017.22it/s]\u001b[A\n",
-      " 15%|█▍        | 347280/2348407 [00:03<01:10, 28280.21it/s]\u001b[A\n",
-      " 15%|█▌        | 361981/2348407 [00:03<00:53, 37198.06it/s]\u001b[A\n",
-      " 16%|█▌        | 377091/2348407 [00:03<00:41, 48068.54it/s]\u001b[A\n",
-      " 17%|█▋        | 392913/2348407 [00:03<00:32, 60758.17it/s]\u001b[A\n",
-      " 17%|█▋        | 407945/2348407 [00:03<00:26, 73852.43it/s]\u001b[A\n",
-      " 18%|█▊        | 423591/2348407 [00:03<00:21, 87751.20it/s]\u001b[A\n",
-      " 19%|█▊        | 439075/2348407 [00:03<00:18, 100860.89it/s]\u001b[A\n",
-      " 19%|█▉        | 454346/2348407 [00:03<00:17, 110329.92it/s]\u001b[A\n",
-      " 20%|█▉        | 469274/2348407 [00:04<00:16, 114103.09it/s]\u001b[A\n",
-      " 21%|██        | 483444/2348407 [00:04<00:16, 115754.26it/s]\u001b[A\n",
-      " 21%|██        | 496957/2348407 [00:04<00:15, 118338.18it/s]\u001b[A\n",
-      " 22%|██▏       | 510161/2348407 [00:04<00:15, 116788.41it/s]\u001b[A\n",
-      " 22%|██▏       | 522804/2348407 [00:04<00:15, 118094.01it/s]\u001b[A\n",
-      " 23%|██▎       | 536220/2348407 [00:04<00:14, 122493.32it/s]\u001b[A\n",
-      " 23%|██▎       | 549468/2348407 [00:04<00:14, 125326.16it/s]\u001b[A\n",
-      " 24%|██▍       | 562978/2348407 [00:04<00:13, 128105.09it/s]\u001b[A\n",
-      " 25%|██▍       | 576549/2348407 [00:04<00:13, 130294.41it/s]\u001b[A\n",
-      " 25%|██▌       | 591884/2348407 [00:05<00:12, 136448.38it/s]\u001b[A\n",
-      " 26%|██▌       | 606693/2348407 [00:05<00:12, 139743.23it/s]\u001b[A\n",
-      " 27%|██▋       | 622764/2348407 [00:05<00:11, 145434.33it/s]\u001b[A\n",
-      " 27%|██▋       | 638910/2348407 [00:05<00:11, 149896.90it/s]\u001b[A\n",
-      " 28%|██▊       | 654357/2348407 [00:05<00:11, 151237.43it/s]\u001b[A\n",
-      " 29%|██▊       | 669597/2348407 [00:05<00:11, 150319.90it/s]\u001b[A\n",
-      " 29%|██▉       | 686291/2348407 [00:05<00:10, 154945.53it/s]\u001b[A\n",
-      " 30%|██▉       | 701879/2348407 [00:05<00:11, 149549.54it/s]\u001b[A\n",
-      " 31%|███       | 716940/2348407 [00:05<00:10, 149664.19it/s]\u001b[A\n",
-      " 31%|███       | 732387/2348407 [00:05<00:10, 151071.53it/s]\u001b[A\n",
-      " 32%|███▏      | 747759/2348407 [00:06<00:10, 151855.19it/s]\u001b[A\n",
-      " 32%|███▏      | 762985/2348407 [00:06<00:10, 151658.89it/s]\u001b[A\n",
-      " 33%|███▎      | 778226/2348407 [00:06<00:10, 151881.76it/s]\u001b[A\n",
-      " 34%|███▍      | 793434/2348407 [00:06<00:10, 151259.28it/s]\u001b[A\n",
-      " 34%|███▍      | 809407/2348407 [00:06<00:10, 153704.25it/s]\u001b[A\n",
-      " 35%|███▌      | 824797/2348407 [00:06<00:09, 153364.80it/s]\u001b[A\n",
-      " 36%|███▌      | 840148/2348407 [00:06<00:09, 151099.91it/s]\u001b[A\n",
-      " 36%|███▋      | 855276/2348407 [00:06<00:10, 141252.22it/s]\u001b[A\n",
-      " 37%|███▋      | 869544/2348407 [00:06<00:10, 138183.24it/s]\u001b[A\n",
-      " 38%|███▊      | 883547/2348407 [00:06<00:10, 138730.50it/s]\u001b[A\n",
-      " 38%|███▊      | 897501/2348407 [00:07<00:10, 134645.33it/s]\u001b[A\n",
-      " 39%|███▉      | 912396/2348407 [00:07<00:10, 138638.52it/s]\u001b[A\n",
-      " 39%|███▉      | 926348/2348407 [00:07<00:10, 133668.27it/s]\u001b[A\n",
-      " 40%|████      | 940609/2348407 [00:07<00:10, 136229.62it/s]\u001b[A\n",
-      " 41%|████      | 955231/2348407 [00:07<00:10, 139073.62it/s]\u001b[A\n",
-      " 41%|████▏     | 969304/2348407 [00:07<00:09, 139565.97it/s]\u001b[A\n",
-      " 42%|████▏     | 983312/2348407 [00:07<00:10, 129966.31it/s]\u001b[A\n",
-      " 42%|████▏     | 996477/2348407 [00:07<00:11, 120475.22it/s]\u001b[A\n",
-      " 43%|████▎     | 1009082/2348407 [00:07<00:10, 122093.52it/s]\u001b[A\n",
-      " 44%|████▎     | 1022840/2348407 [00:08<00:10, 126359.47it/s]\u001b[A\n",
-      " 44%|████▍     | 1035643/2348407 [00:08<00:10, 126167.13it/s]\u001b[A\n",
-      " 45%|████▍     | 1049346/2348407 [00:08<00:10, 129239.39it/s]\u001b[A\n",
-      " 45%|████▌     | 1062468/2348407 [00:08<00:09, 129824.91it/s]\u001b[A\n",
-      " 46%|████▌     | 1075784/2348407 [00:08<00:09, 130654.22it/s]\u001b[A\n",
-      " 46%|████▋     | 1089627/2348407 [00:08<00:09, 132893.60it/s]\u001b[A\n",
-      " 47%|████▋     | 1103999/2348407 [00:08<00:09, 135957.04it/s]\u001b[A\n",
-      " 48%|████▊     | 1119083/2348407 [00:08<00:08, 140102.60it/s]\u001b[A\n",
-      " 48%|████▊     | 1134212/2348407 [00:08<00:08, 143278.67it/s]\u001b[A\n",
-      " 49%|████▉     | 1148905/2348407 [00:08<00:08, 144350.65it/s]\u001b[A\n",
-      " 50%|████▉     | 1164191/2348407 [00:09<00:08, 146799.81it/s]\u001b[A\n",
-      " 50%|█████     | 1179331/2348407 [00:09<00:07, 148148.42it/s]\u001b[A\n",
-      " 51%|█████     | 1195394/2348407 [00:09<00:07, 151681.84it/s]\u001b[A\n",
-      " 52%|█████▏    | 1210836/2348407 [00:09<00:07, 152491.57it/s]\u001b[A\n",
-      " 52%|█████▏    | 1226117/2348407 [00:09<00:07, 150719.57it/s]\u001b[A\n",
-      " 53%|█████▎    | 1241683/2348407 [00:09<00:07, 152168.39it/s]\u001b[A\n",
-      " 54%|█████▎    | 1257332/2348407 [00:09<00:07, 153439.21it/s]\u001b[A\n",
-      " 54%|█████▍    | 1272976/2348407 [00:09<00:06, 154325.16it/s]\u001b[A\n",
-      " 55%|█████▍    | 1288423/2348407 [00:09<00:06, 152609.33it/s]\u001b[A\n",
-      " 56%|█████▌    | 1303698/2348407 [00:09<00:06, 151770.22it/s]\u001b[A\n",
-      " 56%|█████▌    | 1318886/2348407 [00:10<00:06, 150803.79it/s]\u001b[A\n",
-      " 57%|█████▋    | 1334254/2348407 [00:10<00:06, 151654.80it/s]\u001b[A\n",
-      " 57%|█████▋    | 1349713/2348407 [00:10<00:06, 152521.91it/s]\u001b[A\n",
-      " 58%|█████▊    | 1365547/2348407 [00:10<00:06, 153625.33it/s]\u001b[A\n",
-      " 59%|█████▉    | 1381511/2348407 [00:10<00:06, 155380.77it/s]\u001b[A\n",
-      " 59%|█████▉    | 1397059/2348407 [00:10<00:06, 153542.56it/s]\u001b[A\n",
-      " 60%|██████    | 1412781/2348407 [00:10<00:06, 154623.95it/s]\u001b[A\n",
-      " 61%|██████    | 1428254/2348407 [00:10<00:06, 153130.80it/s]\u001b[A\n",
-      " 61%|██████▏   | 1443985/2348407 [00:10<00:05, 154360.56it/s]\u001b[A\n",
-      " 62%|██████▏   | 1459431/2348407 [00:10<00:05, 152894.85it/s]\u001b[A\n",
-      " 63%|██████▎   | 1474731/2348407 [00:11<00:05, 149450.16it/s]\u001b[A\n",
-      " 63%|██████▎   | 1490510/2348407 [00:11<00:05, 151857.68it/s]\u001b[A\n",
-      " 64%|██████▍   | 1506064/2348407 [00:11<00:05, 152941.61it/s]\u001b[A\n",
-      " 65%|██████▍   | 1521379/2348407 [00:11<00:05, 152979.84it/s]\u001b[A\n",
-      " 65%|██████▌   | 1536692/2348407 [00:11<00:05, 150175.14it/s]\u001b[A\n",
-      " 66%|██████▌   | 1552149/2348407 [00:11<00:05, 151464.94it/s]\u001b[A\n",
-      " 67%|██████▋   | 1567353/2348407 [00:11<00:05, 151635.53it/s]\u001b[A\n",
-      " 67%|██████▋   | 1582553/2348407 [00:11<00:05, 151743.43it/s]\u001b[A\n",
-      " 68%|██████▊   | 1598564/2348407 [00:11<00:04, 154157.84it/s]\u001b[A\n",
-      " 69%|██████▊   | 1614159/2348407 [00:11<00:04, 154687.57it/s]\u001b[A\n",
-      " 69%|██████▉   | 1629639/2348407 [00:12<00:04, 150089.91it/s]\u001b[A\n",
-      " 70%|███████   | 1644686/2348407 [00:12<00:04, 141327.25it/s]\u001b[A\n",
-      " 71%|███████   | 1661076/2348407 [00:12<00:04, 147417.66it/s]\u001b[A\n",
-      " 71%|███████▏  | 1675978/2348407 [00:12<00:04, 147703.95it/s]\u001b[A\n",
-      " 72%|███████▏  | 1690960/2348407 [00:12<00:04, 148330.40it/s]\u001b[A\n",
-      " 73%|███████▎  | 1706082/2348407 [00:12<00:04, 149185.14it/s]\u001b[A\n",
-      " 73%|███████▎  | 1721289/2348407 [00:12<00:04, 150036.75it/s]\u001b[A\n",
-      " 74%|███████▍  | 1736820/2348407 [00:12<00:04, 151575.38it/s]\u001b[A\n",
-      " 75%|███████▍  | 1752010/2348407 [00:12<00:04, 145349.01it/s]\u001b[A\n",
-      " 75%|███████▌  | 1766633/2348407 [00:13<00:03, 145611.30it/s]\u001b[A\n",
-      " 76%|███████▌  | 1782060/2348407 [00:13<00:03, 148103.41it/s]\u001b[A\n",
-      " 77%|███████▋  | 1797818/2348407 [00:13<00:03, 150824.14it/s]\u001b[A\n",
-      " 77%|███████▋  | 1812947/2348407 [00:13<00:03, 150829.51it/s]\u001b[A\n",
-      " 78%|███████▊  | 1828648/2348407 [00:13<00:03, 152631.47it/s]\u001b[A\n",
-      " 79%|███████▊  | 1843939/2348407 [00:13<00:03, 148156.05it/s]\u001b[A\n",
-      " 79%|███████▉  | 1859636/2348407 [00:13<00:03, 150693.22it/s]\u001b[A\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      " 80%|███████▉  | 1874750/2348407 [00:13<00:03, 149730.92it/s]\u001b[A\n",
-      " 80%|████████  | 1889756/2348407 [00:13<00:03, 146613.93it/s]\u001b[A\n",
-      " 81%|████████  | 1905305/2348407 [00:13<00:02, 149168.12it/s]\u001b[A\n",
-      " 82%|████████▏ | 1921507/2348407 [00:14<00:02, 152801.85it/s]\u001b[A\n",
-      " 82%|████████▏ | 1936835/2348407 [00:14<00:02, 151560.80it/s]\u001b[A\n",
-      " 83%|████████▎ | 1953000/2348407 [00:14<00:02, 154451.47it/s]\u001b[A\n",
-      " 84%|████████▍ | 1969929/2348407 [00:14<00:02, 158619.91it/s]\u001b[A\n",
-      " 85%|████████▍ | 1985846/2348407 [00:14<00:02, 142345.93it/s]\u001b[A\n",
-      " 85%|████████▌ | 2000833/2348407 [00:14<00:02, 144520.46it/s]\u001b[A\n",
-      " 86%|████████▌ | 2015832/2348407 [00:14<00:02, 146117.69it/s]\u001b[A\n",
-      " 86%|████████▋ | 2030806/2348407 [00:14<00:02, 147185.37it/s]\u001b[A\n",
-      " 87%|████████▋ | 2045655/2348407 [00:14<00:02, 146071.90it/s]\u001b[A\n",
-      " 88%|████████▊ | 2060355/2348407 [00:14<00:01, 145465.87it/s]\u001b[A\n",
-      " 88%|████████▊ | 2074968/2348407 [00:15<00:01, 144337.60it/s]\u001b[A\n",
-      " 89%|████████▉ | 2089450/2348407 [00:15<00:01, 141376.11it/s]\u001b[A\n",
-      " 90%|████████▉ | 2104215/2348407 [00:15<00:01, 142720.34it/s]\u001b[A\n",
-      " 90%|█████████ | 2118524/2348407 [00:15<00:01, 142739.56it/s]\u001b[A\n",
-      " 91%|█████████ | 2132824/2348407 [00:15<00:01, 140369.10it/s]\u001b[A\n",
-      " 91%|█████████▏| 2147809/2348407 [00:15<00:01, 143084.12it/s]\u001b[A\n",
-      " 92%|█████████▏| 2162174/2348407 [00:15<00:01, 143247.12it/s]\u001b[A\n",
-      " 93%|█████████▎| 2176521/2348407 [00:15<00:01, 136299.09it/s]\u001b[A\n",
-      " 93%|█████████▎| 2192431/2348407 [00:15<00:01, 142421.91it/s]\u001b[A\n",
-      " 94%|█████████▍| 2206832/2348407 [00:16<00:00, 142893.78it/s]\u001b[A\n",
-      " 95%|█████████▍| 2221917/2348407 [00:16<00:00, 145189.84it/s]\u001b[A\n",
-      " 95%|█████████▌| 2237484/2348407 [00:16<00:00, 148182.61it/s]\u001b[A\n",
-      " 96%|█████████▌| 2252753/2348407 [00:16<00:00, 149504.90it/s]\u001b[A\n",
-      " 97%|█████████▋| 2267754/2348407 [00:16<00:00, 148489.00it/s]\u001b[A\n",
-      " 97%|█████████▋| 2283269/2348407 [00:16<00:00, 150424.83it/s]\u001b[A\n",
-      " 98%|█████████▊| 2298800/2348407 [00:16<00:00, 151856.47it/s]\u001b[A\n",
-      " 99%|█████████▊| 2314070/2348407 [00:16<00:00, 152105.66it/s]\u001b[A\n",
-      " 99%|█████████▉| 2329843/2348407 [00:16<00:00, 153748.29it/s]\u001b[A\n",
-      "100%|██████████| 2348407/2348407 [00:17<00:00, 137176.83it/s]\u001b[A\n",
-      "\n",
-      "  0%|          | 0/2348407 [00:00<?, ?it/s]\u001b[A\n",
-      "  0%|          | 1/2348407 [00:00<122:14:48,  5.34it/s]\u001b[A\n",
-      "  1%|          | 13674/2348407 [00:00<85:04:33,  7.62it/s]\u001b[A\n",
-      "  1%|          | 22558/2348407 [00:00<59:20:07, 10.89it/s]\u001b[A\n",
-      "  1%|▏         | 33526/2348407 [00:00<41:20:26, 15.55it/s]\u001b[A\n",
-      "  2%|▏         | 47606/2348407 [00:00<28:45:49, 22.22it/s]\u001b[A\n",
-      "  3%|▎         | 62118/2348407 [00:00<20:00:32, 31.74it/s]\u001b[A\n",
-      "  3%|▎         | 73062/2348407 [00:01<13:56:27, 45.34it/s]\u001b[A\n",
-      "  4%|▎         | 86734/2348407 [00:01<9:42:05, 64.76it/s] \u001b[A\n",
-      "  4%|▍         | 99787/2348407 [00:01<6:45:11, 92.49it/s]\u001b[A\n",
-      "  5%|▍         | 114082/2348407 [00:01<4:41:54, 132.09it/s]\u001b[A\n",
-      "  5%|▌         | 128795/2348407 [00:01<3:16:06, 188.63it/s]\u001b[A\n",
-      "  6%|▌         | 143971/2348407 [00:01<2:16:24, 269.33it/s]\u001b[A\n",
-      "  7%|▋         | 157787/2348407 [00:01<1:34:59, 384.36it/s]\u001b[A\n",
-      "  7%|▋         | 170760/2348407 [00:02<1:06:30, 545.70it/s]\u001b[A\n",
-      "  8%|▊         | 184588/2348407 [00:02<46:20, 778.25it/s]  \u001b[A\n",
-      "  8%|▊         | 198535/2348407 [00:02<32:18, 1109.14it/s]\u001b[A\n",
-      "  9%|▉         | 214262/2348407 [00:02<22:30, 1579.71it/s]\u001b[A\n",
-      " 10%|▉         | 230113/2348407 [00:02<15:42, 2247.13it/s]\u001b[A\n",
-      " 10%|█         | 245185/2348407 [00:02<10:59, 3189.80it/s]\u001b[A\n",
-      " 11%|█         | 259438/2348407 [00:02<07:43, 4509.91it/s]\u001b[A\n",
-      " 12%|█▏        | 275130/2348407 [00:02<05:25, 6364.33it/s]\u001b[A\n",
-      " 12%|█▏        | 290593/2348407 [00:03<03:50, 8934.30it/s]\u001b[A\n",
-      " 13%|█▎        | 306036/2348407 [00:03<02:43, 12454.47it/s]\u001b[A\n",
-      " 14%|█▎        | 320996/2348407 [00:03<01:58, 17171.52it/s]\u001b[A\n",
-      " 14%|█▍        | 337265/2348407 [00:03<01:25, 23469.09it/s]\u001b[A\n",
-      " 15%|█▌        | 352968/2348407 [00:03<01:03, 31509.02it/s]\u001b[A\n",
-      " 16%|█▌        | 368831/2348407 [00:03<00:47, 41481.63it/s]\u001b[A\n",
-      " 16%|█▋        | 384391/2348407 [00:03<00:37, 52919.88it/s]\u001b[A\n",
-      " 17%|█▋        | 400578/2348407 [00:03<00:29, 66308.21it/s]\u001b[A\n",
-      " 18%|█▊        | 416175/2348407 [00:03<00:24, 79926.58it/s]\u001b[A\n",
-      " 18%|█▊        | 432651/2348407 [00:03<00:20, 94527.39it/s]\u001b[A\n",
-      " 19%|█▉        | 448459/2348407 [00:04<00:17, 106818.53it/s]\u001b[A\n",
-      " 20%|█▉        | 464125/2348407 [00:04<00:16, 116147.00it/s]\u001b[A\n",
-      " 20%|██        | 479468/2348407 [00:04<00:15, 123781.84it/s]\u001b[A\n",
-      " 21%|██        | 495952/2348407 [00:04<00:13, 133777.26it/s]\u001b[A\n",
-      " 22%|██▏       | 511486/2348407 [00:04<00:13, 138697.37it/s]\u001b[A\n",
-      " 22%|██▏       | 526911/2348407 [00:04<00:12, 141036.55it/s]\u001b[A\n",
-      " 23%|██▎       | 542113/2348407 [00:04<00:13, 136572.31it/s]\u001b[A\n",
-      " 24%|██▎       | 556570/2348407 [00:04<00:13, 135454.45it/s]\u001b[A\n",
-      " 24%|██▍       | 571517/2348407 [00:04<00:12, 139373.23it/s]\u001b[A\n",
-      " 25%|██▌       | 587269/2348407 [00:04<00:12, 144362.19it/s]\u001b[A\n",
-      " 26%|██▌       | 602046/2348407 [00:05<00:12, 145315.50it/s]\u001b[A\n",
-      " 26%|██▋       | 618259/2348407 [00:05<00:11, 149980.54it/s]\u001b[A\n",
-      " 27%|██▋       | 633721/2348407 [00:05<00:11, 151341.12it/s]\u001b[A\n",
-      " 28%|██▊       | 649224/2348407 [00:05<00:11, 152427.44it/s]\u001b[A\n",
-      " 28%|██▊       | 665489/2348407 [00:05<00:10, 155354.93it/s]\u001b[A\n",
-      " 29%|██▉       | 681112/2348407 [00:05<00:10, 153424.96it/s]\u001b[A\n",
-      " 30%|██▉       | 696521/2348407 [00:05<00:11, 150108.99it/s]\u001b[A\n",
-      " 30%|███       | 712307/2348407 [00:05<00:10, 152352.27it/s]\u001b[A\n",
-      " 31%|███       | 727953/2348407 [00:05<00:10, 153560.51it/s]\u001b[A\n",
-      " 32%|███▏      | 743728/2348407 [00:05<00:10, 154792.00it/s]\u001b[A\n",
-      " 32%|███▏      | 759237/2348407 [00:06<00:10, 154418.62it/s]\u001b[A\n",
-      " 33%|███▎      | 775367/2348407 [00:06<00:10, 156418.67it/s]\u001b[A\n",
-      " 34%|███▎      | 791030/2348407 [00:06<00:09, 155970.15it/s]\u001b[A\n",
-      " 34%|███▍      | 806642/2348407 [00:06<00:09, 154586.64it/s]\u001b[A\n",
-      " 35%|███▌      | 822434/2348407 [00:06<00:09, 155569.52it/s]\u001b[A\n",
-      " 36%|███▌      | 838002/2348407 [00:06<00:09, 154807.41it/s]\u001b[A\n",
-      " 36%|███▋      | 853600/2348407 [00:06<00:09, 155155.05it/s]\u001b[A\n",
-      " 37%|███▋      | 869475/2348407 [00:06<00:09, 156214.39it/s]\u001b[A\n",
-      " 38%|███▊      | 885308/2348407 [00:06<00:09, 156842.71it/s]\u001b[A\n",
-      " 38%|███▊      | 900998/2348407 [00:06<00:09, 155491.99it/s]\u001b[A\n",
-      " 39%|███▉      | 916958/2348407 [00:07<00:09, 156701.51it/s]\u001b[A\n",
-      " 40%|███▉      | 932635/2348407 [00:07<00:09, 147808.33it/s]\u001b[A\n",
-      " 40%|████      | 947525/2348407 [00:07<00:09, 143904.09it/s]\u001b[A\n",
-      " 41%|████      | 962679/2348407 [00:07<00:09, 146110.85it/s]\u001b[A\n",
-      " 42%|████▏     | 977368/2348407 [00:07<00:09, 146043.32it/s]\u001b[A\n",
-      " 42%|████▏     | 992027/2348407 [00:07<00:09, 142562.24it/s]\u001b[A\n",
-      " 43%|████▎     | 1006722/2348407 [00:07<00:09, 143838.56it/s]\u001b[A\n",
-      " 43%|████▎     | 1021148/2348407 [00:07<00:09, 143308.87it/s]\u001b[A\n",
-      " 44%|████▍     | 1037744/2348407 [00:07<00:08, 149424.80it/s]\u001b[A\n",
-      " 45%|████▍     | 1052776/2348407 [00:08<00:08, 148950.06it/s]\u001b[A\n",
-      " 45%|████▌     | 1067734/2348407 [00:08<00:08, 146564.16it/s]\u001b[A\n",
-      " 46%|████▌     | 1082443/2348407 [00:08<00:08, 144320.67it/s]\u001b[A\n",
-      " 47%|████▋     | 1097454/2348407 [00:08<00:08, 146005.40it/s]\u001b[A\n",
-      " 47%|████▋     | 1113242/2348407 [00:08<00:08, 149374.21it/s]\u001b[A\n",
-      " 48%|████▊     | 1128768/2348407 [00:08<00:08, 151092.18it/s]\u001b[A\n",
-      " 49%|████▊     | 1143927/2348407 [00:08<00:07, 151238.96it/s]\u001b[A\n",
-      " 49%|████▉     | 1159076/2348407 [00:08<00:07, 150622.62it/s]\u001b[A\n",
-      " 50%|█████     | 1174791/2348407 [00:08<00:07, 152522.38it/s]\u001b[A\n",
-      " 51%|█████     | 1190996/2348407 [00:08<00:07, 155260.43it/s]\u001b[A\n",
-      " 51%|█████▏    | 1206592/2348407 [00:09<00:07, 155469.13it/s]\u001b[A\n",
-      " 52%|█████▏    | 1223336/2348407 [00:09<00:07, 158874.40it/s]\u001b[A\n",
-      " 53%|█████▎    | 1239255/2348407 [00:09<00:07, 157809.80it/s]\u001b[A\n",
-      " 53%|█████▎    | 1255132/2348407 [00:09<00:06, 158094.43it/s]\u001b[A\n",
-      " 54%|█████▍    | 1270958/2348407 [00:09<00:06, 157692.49it/s]\u001b[A\n",
-      " 55%|█████▍    | 1286990/2348407 [00:09<00:06, 158471.14it/s]\u001b[A\n",
-      " 55%|█████▌    | 1302847/2348407 [00:09<00:06, 158335.56it/s]\u001b[A\n",
-      " 56%|█████▌    | 1318687/2348407 [00:09<00:06, 156067.87it/s]\u001b[A\n",
-      " 57%|█████▋    | 1334306/2348407 [00:09<00:06, 154354.28it/s]\u001b[A\n",
-      " 57%|█████▋    | 1350311/2348407 [00:09<00:06, 156018.60it/s]\u001b[A\n",
-      " 58%|█████▊    | 1365926/2348407 [00:10<00:06, 155505.13it/s]\u001b[A\n",
-      " 59%|█████▉    | 1382434/2348407 [00:10<00:06, 158257.53it/s]\u001b[A\n",
-      " 60%|█████▉    | 1398311/2348407 [00:10<00:05, 158410.41it/s]\u001b[A\n",
-      " 60%|██████    | 1414166/2348407 [00:10<00:05, 157835.27it/s]\u001b[A\n",
-      " 61%|██████    | 1430661/2348407 [00:10<00:05, 159902.01it/s]\u001b[A\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      " 62%|██████▏   | 1446665/2348407 [00:10<00:05, 159034.09it/s]\u001b[A\n",
-      " 62%|██████▏   | 1463614/2348407 [00:10<00:05, 162032.79it/s]\u001b[A\n",
-      " 63%|██████▎   | 1479839/2348407 [00:10<00:05, 161397.98it/s]\u001b[A\n",
-      " 64%|██████▎   | 1495995/2348407 [00:10<00:05, 161235.36it/s]\u001b[A\n",
-      " 64%|██████▍   | 1512130/2348407 [00:10<00:05, 157635.41it/s]\u001b[A\n",
-      " 65%|██████▌   | 1527919/2348407 [00:11<00:05, 155374.49it/s]\u001b[A\n",
-      " 66%|██████▌   | 1544745/2348407 [00:11<00:05, 159027.98it/s]\u001b[A\n",
-      " 66%|██████▋   | 1560687/2348407 [00:11<00:04, 158568.45it/s]\u001b[A\n",
-      " 67%|██████▋   | 1576572/2348407 [00:11<00:04, 156670.52it/s]\u001b[A\n",
-      " 68%|██████▊   | 1592264/2348407 [00:11<00:04, 152788.03it/s]\u001b[A\n",
-      " 68%|██████▊   | 1607638/2348407 [00:11<00:04, 153071.44it/s]\u001b[A\n",
-      " 69%|██████▉   | 1622973/2348407 [00:11<00:05, 141496.29it/s]\u001b[A\n",
-      " 70%|██████▉   | 1637488/2348407 [00:11<00:04, 142572.18it/s]\u001b[A\n",
-      " 70%|███████   | 1651882/2348407 [00:11<00:05, 136892.52it/s]\u001b[A\n",
-      " 71%|███████   | 1665716/2348407 [00:12<00:05, 135401.01it/s]\u001b[A\n",
-      " 72%|███████▏  | 1679800/2348407 [00:12<00:04, 136986.50it/s]\u001b[A\n",
-      " 72%|███████▏  | 1693577/2348407 [00:12<00:04, 133456.91it/s]\u001b[A\n",
-      " 73%|███████▎  | 1708132/2348407 [00:12<00:04, 136867.28it/s]\u001b[A\n",
-      " 73%|███████▎  | 1721893/2348407 [00:12<00:04, 136559.95it/s]\u001b[A\n",
-      " 74%|███████▍  | 1735945/2348407 [00:12<00:04, 137723.21it/s]\u001b[A\n",
-      " 75%|███████▍  | 1751352/2348407 [00:12<00:04, 142249.87it/s]\u001b[A\n",
-      " 75%|███████▌  | 1767917/2348407 [00:12<00:03, 148543.72it/s]\u001b[A\n",
-      " 76%|███████▌  | 1783324/2348407 [00:12<00:03, 150158.21it/s]\u001b[A\n",
-      " 77%|███████▋  | 1799371/2348407 [00:12<00:03, 153109.06it/s]\u001b[A\n",
-      " 77%|███████▋  | 1814977/2348407 [00:13<00:03, 153980.65it/s]\u001b[A\n",
-      " 78%|███████▊  | 1831091/2348407 [00:13<00:03, 156057.66it/s]\u001b[A\n",
-      " 79%|███████▊  | 1846741/2348407 [00:13<00:03, 151055.84it/s]\u001b[A\n",
-      " 79%|███████▉  | 1861913/2348407 [00:13<00:03, 147575.76it/s]\u001b[A\n",
-      " 80%|███████▉  | 1877555/2348407 [00:13<00:03, 150121.32it/s]\u001b[A\n",
-      " 81%|████████  | 1892623/2348407 [00:13<00:03, 145942.16it/s]\u001b[A\n",
-      " 81%|████████▏ | 1908160/2348407 [00:13<00:02, 148646.76it/s]\u001b[A\n",
-      " 82%|████████▏ | 1924474/2348407 [00:13<00:02, 152715.35it/s]\u001b[A\n",
-      " 83%|████████▎ | 1939814/2348407 [00:13<00:02, 149048.26it/s]\u001b[A\n",
-      " 83%|████████▎ | 1956715/2348407 [00:13<00:02, 154522.61it/s]\u001b[A\n",
-      " 84%|████████▍ | 1972266/2348407 [00:14<00:02, 154686.37it/s]\u001b[A\n",
-      " 85%|████████▍ | 1988170/2348407 [00:14<00:02, 155967.16it/s]\u001b[A\n",
-      " 85%|████████▌ | 2003818/2348407 [00:14<00:02, 156049.76it/s]\u001b[A\n",
-      " 86%|████████▌ | 2019459/2348407 [00:14<00:02, 151966.04it/s]\u001b[A\n",
-      " 87%|████████▋ | 2035136/2348407 [00:14<00:02, 153374.78it/s]\u001b[A\n",
-      " 87%|████████▋ | 2050511/2348407 [00:14<00:02, 146351.53it/s]\u001b[A\n",
-      " 88%|████████▊ | 2065710/2348407 [00:14<00:01, 147997.76it/s]\u001b[A\n",
-      " 89%|████████▊ | 2080580/2348407 [00:14<00:01, 147110.28it/s]\u001b[A\n",
-      " 89%|████████▉ | 2095418/2348407 [00:14<00:01, 147488.01it/s]\u001b[A\n",
-      " 90%|████████▉ | 2111462/2348407 [00:15<00:01, 151146.99it/s]\u001b[A\n",
-      " 91%|█████████ | 2126624/2348407 [00:15<00:01, 150346.10it/s]\u001b[A\n",
-      " 91%|█████████ | 2142400/2348407 [00:15<00:01, 152488.08it/s]\u001b[A\n",
-      " 92%|█████████▏| 2158274/2348407 [00:15<00:01, 154309.55it/s]\u001b[A\n",
-      " 93%|█████████▎| 2174113/2348407 [00:15<00:01, 155509.95it/s]\u001b[A\n",
-      " 93%|█████████▎| 2190461/2348407 [00:15<00:01, 157816.46it/s]\u001b[A\n",
-      " 94%|█████████▍| 2206266/2348407 [00:15<00:00, 150780.20it/s]\u001b[A\n",
-      " 95%|█████████▍| 2221427/2348407 [00:15<00:01, 110135.91it/s]\u001b[A\n",
-      " 95%|█████████▌| 2235760/2348407 [00:15<00:00, 118358.63it/s]\u001b[A\n",
-      " 96%|█████████▌| 2249034/2348407 [00:16<00:00, 122329.72it/s]\u001b[A\n",
-      " 96%|█████████▋| 2263300/2348407 [00:16<00:00, 127792.26it/s]\u001b[A\n",
-      " 97%|█████████▋| 2280645/2348407 [00:16<00:00, 138748.19it/s]\u001b[A\n",
-      " 98%|█████████▊| 2295305/2348407 [00:16<00:00, 139385.77it/s]\u001b[A\n",
-      " 98%|█████████▊| 2313046/2348407 [00:16<00:00, 148963.22it/s]\u001b[A\n",
-      " 99%|█████████▉| 2329524/2348407 [00:16<00:00, 153379.10it/s]\u001b[A\n",
-      "100%|██████████| 2348407/2348407 [00:16<00:00, 139368.26it/s]\u001b[A\n"
-     ]
-    }
-   ],
-   "source": [
-    "# get ontology classes\n",
-    "class_list = set([x for x in mondo_graph.subjects(RDF.type, OWL.Class)])\n",
-    "\n",
-    "# get dbxrefs\n",
-    "dbxref_res = [x for x in tqdm(mondo_graph) if 'MONDO' in x[0] and 'hasdbxref' in str(x[1]).lower()]\n",
-    "dbxref_uris = {str(x[2]).lower(): str(x[0]) for x in dbxref_res}\n",
-    "\n",
-    "# get exact matches\n",
-    "exact_res = [x for x in tqdm(mondo_graph) if 'MONDO' in x[0] and 'exactmatch' in str(x[1]).lower()]\n",
-    "exact_uris = {str(x[2]).lower(): str(x[0]) for x in exact_res}\n",
-    "\n",
-    "# combine into single dict\n",
-    "mondo_dbxref_dict = {**dbxref_uris, **exact_uris}\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 86,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "{'ncit:c84665': 'http://purl.obolibrary.org/obo/MONDO_0007417',\n",
-       " 'doid:0110058': 'http://purl.obolibrary.org/obo/MONDO_0010521',\n",
-       " 'doid:0050831': 'http://purl.obolibrary.org/obo/MONDO_0011412',\n",
-       " 'umls:c1854663': 'http://purl.obolibrary.org/obo/MONDO_0009671',\n",
-       " 'sctid:89454001': 'http://purl.obolibrary.org/obo/MONDO_0009833',\n",
-       " 'sctid:363379000': 'http://purl.obolibrary.org/obo/MONDO_0004700',\n",
-       " 'efo:1001013': 'http://purl.obolibrary.org/obo/MONDO_0006828',\n",
-       " 'doid:0060399': 'http://purl.obolibrary.org/obo/MONDO_0007631',\n",
-       " 'ncit:c5374': 'http://purl.obolibrary.org/obo/MONDO_0004206',\n",
-       " 'omim:312830': 'http://purl.obolibrary.org/obo/MONDO_0010728',\n",
-       " 'gard:0009366': 'http://purl.obolibrary.org/obo/MONDO_0006360',\n",
-       " 'sctid:254960002': 'http://purl.obolibrary.org/obo/MONDO_0019612',\n",
-       " 'icd9:703.8': 'http://purl.obolibrary.org/obo/MONDO_0008060',\n",
-       " 'umls:cn205422': 'http://purl.obolibrary.org/obo/MONDO_0018984',\n",
-       " 'mesh:d001260': 'http://purl.obolibrary.org/obo/MONDO_0008840',\n",
-       " 'umls:c2931580': 'http://purl.obolibrary.org/obo/MONDO_0022552',\n",
-       " 'icd10:d84.1': 'http://purl.obolibrary.org/obo/MONDO_0019623',\n",
-       " 'orphanet:88639': 'http://purl.obolibrary.org/obo/MONDO_0009603',\n",
-       " 'sctid:186963008': 'http://purl.obolibrary.org/obo/MONDO_0006865',\n",
-       " 'doid:3855': 'http://purl.obolibrary.org/obo/MONDO_0002790',\n",
-       " 'ncit:c2939': 'http://purl.obolibrary.org/obo/MONDO_0000751',\n",
-       " 'umls:cn199359': 'http://purl.obolibrary.org/obo/MONDO_0015329',\n",
-       " 'umls:c0151295': 'http://purl.obolibrary.org/obo/MONDO_0002128',\n",
-       " 'icd10:l90.8': 'http://purl.obolibrary.org/obo/MONDO_0008716',\n",
-       " 'icd9:610.1': 'http://purl.obolibrary.org/obo/MONDO_0005219',\n",
-       " 'icd9:377.22': 'http://purl.obolibrary.org/obo/MONDO_0001679',\n",
-       " 'icd10:q87.8': 'http://purl.obolibrary.org/obo/MONDO_0015326',\n",
-       " 'orphanet:2470': 'http://purl.obolibrary.org/obo/MONDO_0011010',\n",
-       " 'doid:0110826': 'http://purl.obolibrary.org/obo/MONDO_0010168',\n",
-       " 'umls:c1334362': 'http://purl.obolibrary.org/obo/MONDO_0002961',\n",
-       " 'umls:c1833796': 'http://purl.obolibrary.org/obo/MONDO_0008137',\n",
-       " 'gard:0000329': 'http://purl.obolibrary.org/obo/MONDO_0019547',\n",
-       " 'icd10:q77.3': 'http://purl.obolibrary.org/obo/MONDO_0009189',\n",
-       " 'ncit:c34347': 'http://purl.obolibrary.org/obo/MONDO_0020599',\n",
-       " 'icd10:a85.8': 'http://purl.obolibrary.org/obo/MONDO_0019384',\n",
-       " 'icd9:742.4': 'http://purl.obolibrary.org/obo/MONDO_0010233',\n",
-       " 'umls:c2678477': 'http://purl.obolibrary.org/obo/MONDO_0012743',\n",
-       " 'orphanet:472': 'http://purl.obolibrary.org/obo/MONDO_0018769',\n",
-       " 'orphanet:434809': 'http://purl.obolibrary.org/obo/MONDO_0018558',\n",
-       " 'gard:0007354': 'http://purl.obolibrary.org/obo/MONDO_0019324',\n",
-       " 'doid:2024': 'http://purl.obolibrary.org/obo/MONDO_0006374',\n",
-       " 'orphanet:101063': 'http://purl.obolibrary.org/obo/MONDO_0010029',\n",
-       " 'gard:0009716': 'http://purl.obolibrary.org/obo/MONDO_0021935',\n",
-       " 'umls:cn227205': 'http://purl.obolibrary.org/obo/MONDO_0017761',\n",
-       " 'ncit:c4011': 'http://purl.obolibrary.org/obo/MONDO_0002794',\n",
-       " 'ncit:c93210': 'http://purl.obolibrary.org/obo/MONDO_0021166',\n",
-       " 'icd10:o00.9': 'http://purl.obolibrary.org/obo/MONDO_0000755',\n",
-       " 'csp:2000-1867': 'http://purl.obolibrary.org/obo/MONDO_0004993',\n",
-       " 'umls:c1840387': 'http://purl.obolibrary.org/obo/MONDO_0007775',\n",
-       " 'efo:0007168': 'http://purl.obolibrary.org/obo/MONDO_0015274',\n",
-       " 'sctid:254609000': 'http://purl.obolibrary.org/obo/MONDO_0017590',\n",
-       " 'icd9:379.59': 'http://purl.obolibrary.org/obo/MONDO_0024363',\n",
-       " 'gard:0001502': 'http://purl.obolibrary.org/obo/MONDO_0015183',\n",
-       " 'icd10:l90.2': 'http://purl.obolibrary.org/obo/MONDO_0016444',\n",
-       " 'sctid:254151006': 'http://purl.obolibrary.org/obo/MONDO_0010201',\n",
-       " 'umls:c1847731': 'http://purl.obolibrary.org/obo/MONDO_0011692',\n",
-       " 'sctid:28366008': 'http://purl.obolibrary.org/obo/MONDO_0001277',\n",
-       " 'mesh:d001248': 'http://purl.obolibrary.org/obo/MONDO_0000950',\n",
-       " 'omim:257150': 'http://purl.obolibrary.org/obo/MONDO_0009755',\n",
-       " 'icd9:286': 'http://purl.obolibrary.org/obo/MONDO_0001531',\n",
-       " 'hp:0100324': 'http://purl.obolibrary.org/obo/MONDO_0019340',\n",
-       " 'umls:c1370504': 'http://purl.obolibrary.org/obo/MONDO_0004383',\n",
-       " 'icd10:e16.1': 'http://purl.obolibrary.org/obo/MONDO_0011236',\n",
-       " 'omim:613977': 'http://purl.obolibrary.org/obo/MONDO_0013511',\n",
-       " 'ncit:c7543': 'http://purl.obolibrary.org/obo/MONDO_0008627',\n",
-       " 'mesh:c537452': 'http://purl.obolibrary.org/obo/MONDO_0012272',\n",
-       " 'gard:0000716': 'http://purl.obolibrary.org/obo/MONDO_0022462',\n",
-       " 'icd10:f60.7': 'http://purl.obolibrary.org/obo/MONDO_0001157',\n",
-       " 'umls:c0155746': 'http://purl.obolibrary.org/obo/MONDO_0002641',\n",
-       " 'doid:0110873': 'http://purl.obolibrary.org/obo/MONDO_0012563',\n",
-       " 'orphanet:284963': 'http://purl.obolibrary.org/obo/MONDO_0007947',\n",
-       " 'sctid:700056005': 'http://purl.obolibrary.org/obo/MONDO_0000141',\n",
-       " 'icd10:c91.90': 'http://purl.obolibrary.org/obo/MONDO_0004967',\n",
-       " 'mesh:c563423': 'http://purl.obolibrary.org/obo/MONDO_0010961',\n",
-       " 'sctid:239112008': 'http://purl.obolibrary.org/obo/MONDO_0018115',\n",
-       " 'sctid:254731001': 'http://purl.obolibrary.org/obo/MONDO_0000930',\n",
-       " 'icd9:288.50': 'http://purl.obolibrary.org/obo/MONDO_0003785',\n",
-       " 'icd10:q87.5': 'http://purl.obolibrary.org/obo/MONDO_0010538',\n",
-       " 'umls:c0155501': 'http://purl.obolibrary.org/obo/MONDO_0004900',\n",
-       " 'omim:603388': 'http://purl.obolibrary.org/obo/MONDO_0011314',\n",
-       " 'icd10:b45.8': 'http://purl.obolibrary.org/obo/MONDO_0005724',\n",
-       " 'mesh:d016538': 'http://purl.obolibrary.org/obo/MONDO_0009662',\n",
-       " 'gard:0003521': 'http://purl.obolibrary.org/obo/MONDO_0019778',\n",
-       " 'doid:7166': 'http://purl.obolibrary.org/obo/MONDO_0004126',\n",
-       " 'umls:c1866483': 'http://purl.obolibrary.org/obo/MONDO_0011129',\n",
-       " 'doid:0110509': 'http://purl.obolibrary.org/obo/MONDO_0012333',\n",
-       " 'gard:0012155': 'http://purl.obolibrary.org/obo/MONDO_0013480',\n",
-       " 'meddra:10014071': 'http://purl.obolibrary.org/obo/MONDO_0005737',\n",
-       " 'umls:c4072980': 'http://purl.obolibrary.org/obo/MONDO_0019516',\n",
-       " 'gard:0007794': 'http://purl.obolibrary.org/obo/MONDO_0006474',\n",
-       " 'umls:c3279807': 'http://purl.obolibrary.org/obo/MONDO_0013573',\n",
-       " 'umls:c0268390': 'http://purl.obolibrary.org/obo/MONDO_0008633',\n",
-       " 'mesh:d016510': 'http://purl.obolibrary.org/obo/MONDO_0006713',\n",
-       " 'omim:210700': 'http://purl.obolibrary.org/obo/MONDO_0008870',\n",
-       " 'icd10:l98.1': 'http://purl.obolibrary.org/obo/MONDO_0006586',\n",
-       " 'omim:607278': 'http://purl.obolibrary.org/obo/MONDO_0011806',\n",
-       " 'orphanet:832': 'http://purl.obolibrary.org/obo/MONDO_0009492',\n",
-       " 'sctid:766709000': 'http://purl.obolibrary.org/obo/MONDO_0016053',\n",
-       " 'ncit:c3148': 'http://purl.obolibrary.org/obo/MONDO_0005173',\n",
-       " 'umls:cn227261': 'http://purl.obolibrary.org/obo/MONDO_0018093',\n",
-       " 'icd10:q77.7': 'http://purl.obolibrary.org/obo/MONDO_0018490',\n",
-       " 'sctid:72409005': 'http://purl.obolibrary.org/obo/MONDO_0005945',\n",
-       " 'icd10:g71.2': 'http://purl.obolibrary.org/obo/MONDO_0012929',\n",
-       " 'sctid:609571007': 'http://purl.obolibrary.org/obo/MONDO_0011667',\n",
-       " 'orphanet:83593': 'http://purl.obolibrary.org/obo/MONDO_0019380',\n",
-       " 'omim:616760': 'http://purl.obolibrary.org/obo/MONDO_0014765',\n",
-       " 'umls:c0346110': 'http://purl.obolibrary.org/obo/MONDO_0003805',\n",
-       " 'ncit:c7695': 'http://purl.obolibrary.org/obo/MONDO_0018368',\n",
-       " 'umls:c1840321': 'http://purl.obolibrary.org/obo/MONDO_0007799',\n",
-       " 'orphanet:1946': 'http://purl.obolibrary.org/obo/MONDO_0009185',\n",
-       " 'ncit:c54287': 'http://purl.obolibrary.org/obo/MONDO_0002831',\n",
-       " 'orphanet:330032': 'http://purl.obolibrary.org/obo/MONDO_0018022',\n",
-       " 'icd10:q37.1': 'http://purl.obolibrary.org/obo/MONDO_0016034',\n",
-       " 'oncotree:grct': 'http://purl.obolibrary.org/obo/MONDO_0006036',\n",
-       " 'umls:c3151055': 'http://purl.obolibrary.org/obo/MONDO_0013400',\n",
-       " 'sctid:719213009': 'http://purl.obolibrary.org/obo/MONDO_0011046',\n",
-       " 'umls:c1802405': 'http://purl.obolibrary.org/obo/MONDO_0010090',\n",
-       " 'gard:0010033': 'http://purl.obolibrary.org/obo/MONDO_0012053',\n",
-       " 'sctid:724063005': 'http://purl.obolibrary.org/obo/MONDO_0018837',\n",
-       " 'orphanet:2752': 'http://purl.obolibrary.org/obo/MONDO_0009793',\n",
-       " 'mesh:c565776': 'http://purl.obolibrary.org/obo/MONDO_0011459',\n",
-       " 'sctid:65038009': 'http://purl.obolibrary.org/obo/MONDO_0006553',\n",
-       " 'mesh:d013064': 'http://purl.obolibrary.org/obo/MONDO_0004730',\n",
-       " 'sctid:68409003': 'http://purl.obolibrary.org/obo/MONDO_0056821',\n",
-       " 'sctid:723306004': 'http://purl.obolibrary.org/obo/MONDO_0019405',\n",
-       " 'umls:c1855793': 'http://purl.obolibrary.org/obo/MONDO_0009438',\n",
-       " 'mesh:d020423': 'http://purl.obolibrary.org/obo/MONDO_0003598',\n",
-       " 'meddra:10062901': 'http://purl.obolibrary.org/obo/MONDO_0007893',\n",
-       " 'icd10:e77.8': 'http://purl.obolibrary.org/obo/MONDO_0019366',\n",
-       " 'sctid:763366000': 'http://purl.obolibrary.org/obo/MONDO_0013971',\n",
-       " 'ncit:c34733': 'http://purl.obolibrary.org/obo/MONDO_0002803',\n",
-       " 'doid:13687': 'http://purl.obolibrary.org/obo/MONDO_0001775',\n",
-       " 'orphanet:101997': 'http://purl.obolibrary.org/obo/MONDO_0003778',\n",
-       " 'hp:0000545': 'http://purl.obolibrary.org/obo/MONDO_0001384',\n",
-       " 'sctid:404086000': 'http://purl.obolibrary.org/obo/MONDO_0006351',\n",
-       " 'icd10:q82.5': 'http://purl.obolibrary.org/obo/MONDO_0008094',\n",
-       " 'icd10:d70.4': 'http://purl.obolibrary.org/obo/MONDO_0008090',\n",
-       " 'sctid:218358001': 'http://purl.obolibrary.org/obo/MONDO_0006562',\n",
-       " 'omim:610505': 'http://purl.obolibrary.org/obo/MONDO_0012512',\n",
-       " 'ncit:c4380': 'http://purl.obolibrary.org/obo/MONDO_0003319',\n",
-       " 'icd10:q81.1': 'http://purl.obolibrary.org/obo/MONDO_0009182',\n",
-       " 'umls:c0025299': 'http://purl.obolibrary.org/obo/MONDO_0001147',\n",
-       " 'umls:c3150926': 'http://purl.obolibrary.org/obo/MONDO_0013355',\n",
-       " 'icd10:j04': 'http://purl.obolibrary.org/obo/MONDO_0000263',\n",
-       " 'sctid:734016004': 'http://purl.obolibrary.org/obo/MONDO_0012574',\n",
-       " 'umls:c1334659': 'http://purl.obolibrary.org/obo/MONDO_0003284',\n",
-       " 'efo:1001997': 'http://purl.obolibrary.org/obo/MONDO_0009279',\n",
-       " 'doid:0110242': 'http://purl.obolibrary.org/obo/MONDO_0007289',\n",
-       " 'sctid:35091000119101': 'http://purl.obolibrary.org/obo/MONDO_0020214',\n",
-       " 'umls:c0011636': 'http://purl.obolibrary.org/obo/MONDO_0004678',\n",
-       " 'gard:0000830': 'http://purl.obolibrary.org/obo/MONDO_0011127',\n",
-       " 'umls:c1868546': 'http://purl.obolibrary.org/obo/MONDO_0008211',\n",
-       " 'omim:615102': 'http://purl.obolibrary.org/obo/MONDO_0014044',\n",
-       " 'doid:0060373': 'http://purl.obolibrary.org/obo/MONDO_0009793',\n",
-       " 'sctid:302919001': 'http://purl.obolibrary.org/obo/MONDO_0000286',\n",
-       " 'meddra:10010439': 'http://purl.obolibrary.org/obo/MONDO_0005711',\n",
-       " 'doid:3327': 'http://purl.obolibrary.org/obo/MONDO_0006891',\n",
-       " 'icd9:201': 'http://purl.obolibrary.org/obo/MONDO_0004952',\n",
-       " 'omim:265300': 'http://purl.obolibrary.org/obo/MONDO_0009933',\n",
-       " 'umls:c0006285': 'http://purl.obolibrary.org/obo/MONDO_0005682',\n",
-       " 'orphanet:1727': 'http://purl.obolibrary.org/obo/MONDO_0012020',\n",
-       " 'efo:0007547': 'http://purl.obolibrary.org/obo/MONDO_0006018',\n",
-       " 'orphanet:1790': 'http://purl.obolibrary.org/obo/MONDO_0009425',\n",
-       " 'efo:0007175': 'http://purl.obolibrary.org/obo/MONDO_0005673',\n",
-       " 'umls:c1335061': 'http://purl.obolibrary.org/obo/MONDO_0003051',\n",
-       " 'doid:0110591': 'http://purl.obolibrary.org/obo/MONDO_0011074',\n",
-       " 'icd10:q71.8': 'http://purl.obolibrary.org/obo/MONDO_0017516',\n",
-       " 'doid:0060262': 'http://purl.obolibrary.org/obo/MONDO_0005281',\n",
-       " 'gard:0011890': 'http://purl.obolibrary.org/obo/MONDO_0019234',\n",
-       " 'icd10:c62.1': 'http://purl.obolibrary.org/obo/MONDO_0006447',\n",
-       " 'mesh:c566281': 'http://purl.obolibrary.org/obo/MONDO_0007120',\n",
-       " 'sctid:41495000': 'http://purl.obolibrary.org/obo/MONDO_0019212',\n",
-       " 'cohd:4181327': 'http://purl.obolibrary.org/obo/MONDO_0005411',\n",
-       " 'umls:c1864839': 'http://purl.obolibrary.org/obo/MONDO_0012513',\n",
-       " 'orphanet:261911': 'http://purl.obolibrary.org/obo/MONDO_0016889',\n",
-       " 'orphanet:447771': 'http://purl.obolibrary.org/obo/MONDO_0018646',\n",
-       " 'umls:c1509147': 'http://purl.obolibrary.org/obo/MONDO_0005509',\n",
-       " 'doid:3193': 'http://purl.obolibrary.org/obo/MONDO_0002547',\n",
-       " 'ncit:c4122': 'http://purl.obolibrary.org/obo/MONDO_0006350',\n",
-       " 'omim:606324': 'http://purl.obolibrary.org/obo/MONDO_0011658',\n",
-       " 'ncit:c6890': 'http://purl.obolibrary.org/obo/MONDO_0003867',\n",
-       " 'umls:c1335387': 'http://purl.obolibrary.org/obo/MONDO_0003327',\n",
-       " 'orphanet:57': 'http://purl.obolibrary.org/obo/MONDO_0012747',\n",
-       " 'efo:1000522': 'http://purl.obolibrary.org/obo/MONDO_0003055',\n",
-       " 'gard:0000247': 'http://purl.obolibrary.org/obo/MONDO_0010728',\n",
-       " 'ncit:c118755': 'http://purl.obolibrary.org/obo/MONDO_0005464',\n",
-       " 'mesh:c566576': 'http://purl.obolibrary.org/obo/MONDO_0011196',\n",
-       " 'doid:445': 'http://purl.obolibrary.org/obo/MONDO_0015231',\n",
-       " 'sctid:766759009': 'http://purl.obolibrary.org/obo/MONDO_0016095',\n",
-       " 'omim:148190': 'http://purl.obolibrary.org/obo/MONDO_0007848',\n",
-       " 'ncit:c27319': 'http://purl.obolibrary.org/obo/MONDO_0003827',\n",
-       " 'efo:1000508': 'http://purl.obolibrary.org/obo/MONDO_0006397',\n",
-       " 'umls:cn203276': 'http://purl.obolibrary.org/obo/MONDO_0017544',\n",
-       " 'efo:0000272': 'http://purl.obolibrary.org/obo/MONDO_0021636',\n",
-       " 'efo:0006452': 'http://purl.obolibrary.org/obo/MONDO_0005599',\n",
-       " 'ncit:c7981': 'http://purl.obolibrary.org/obo/MONDO_0020653',\n",
-       " 'icd10:q23.8': 'http://purl.obolibrary.org/obo/MONDO_0020404',\n",
-       " 'icd10:q82.8': 'http://purl.obolibrary.org/obo/MONDO_0018853',\n",
-       " 'doid:0060768': 'http://purl.obolibrary.org/obo/MONDO_0008434',\n",
-       " 'umls:c4084709': 'http://purl.obolibrary.org/obo/MONDO_0014739',\n",
-       " 'icd10:i45.8': 'http://purl.obolibrary.org/obo/MONDO_0013279',\n",
-       " 'efo:0005772': 'http://purl.obolibrary.org/obo/MONDO_0005559',\n",
-       " 'gard:0010568': 'http://purl.obolibrary.org/obo/MONDO_0012722',\n",
-       " 'umls:c1332206': 'http://purl.obolibrary.org/obo/MONDO_0003660',\n",
-       " 'omim:211370': 'http://purl.obolibrary.org/obo/MONDO_0008884',\n",
-       " 'icd9:365.89': 'http://purl.obolibrary.org/obo/MONDO_0004930',\n",
-       " 'doid:14365': 'http://purl.obolibrary.org/obo/MONDO_0008919',\n",
-       " 'omim:102350': 'http://purl.obolibrary.org/obo/MONDO_0007054',\n",
-       " 'gard:0004883': 'http://purl.obolibrary.org/obo/MONDO_0010029',\n",
-       " 'umls:c1332191': 'http://purl.obolibrary.org/obo/MONDO_0003706',\n",
-       " 'sctid:73391008': 'http://purl.obolibrary.org/obo/MONDO_0006736',\n",
-       " 'omim:607903': 'http://purl.obolibrary.org/obo/MONDO_0011932',\n",
-       " 'icd10:q23.3': 'http://purl.obolibrary.org/obo/MONDO_0019817',\n",
-       " 'umls:cn199179': 'http://purl.obolibrary.org/obo/MONDO_0015265',\n",
-       " 'orphanet:217629': 'http://purl.obolibrary.org/obo/MONDO_0016338',\n",
-       " 'sctid:2884008': 'http://purl.obolibrary.org/obo/MONDO_0018096',\n",
-       " 'orphanet:306741': 'http://purl.obolibrary.org/obo/MONDO_0017649',\n",
-       " 'icdo:8145/3': 'http://purl.obolibrary.org/obo/MONDO_0021652',\n",
-       " 'gard:0006985': 'http://purl.obolibrary.org/obo/MONDO_0011244',\n",
-       " 'omim:610997': 'http://purl.obolibrary.org/obo/MONDO_0012597',\n",
-       " 'umls:c2751312': 'http://purl.obolibrary.org/obo/MONDO_0000909',\n",
-       " 'orphanet:746': 'http://purl.obolibrary.org/obo/MONDO_0012172',\n",
-       " 'wikipedia:inverted_follicular_keratosis': 'http://purl.obolibrary.org/obo/MONDO_0006563',\n",
-       " 'doid:5150': 'http://purl.obolibrary.org/obo/MONDO_0003303',\n",
-       " 'icd10:j84.1': 'http://purl.obolibrary.org/obo/MONDO_0009887',\n",
-       " 'mesh:d002270': 'http://purl.obolibrary.org/obo/MONDO_0002237',\n",
-       " 'meddra:10024381': 'http://purl.obolibrary.org/obo/MONDO_0019046',\n",
-       " 'omim:226200': 'http://purl.obolibrary.org/obo/MONDO_0009173',\n",
-       " 'gard:0008595': 'http://purl.obolibrary.org/obo/MONDO_0010188',\n",
-       " 'ncit:c3891': 'http://purl.obolibrary.org/obo/MONDO_0001703',\n",
-       " 'orphanet:65288': 'http://purl.obolibrary.org/obo/MONDO_0012192',\n",
-       " 'orphanet:725': 'http://purl.obolibrary.org/obo/MONDO_0019123',\n",
-       " 'icd9:478.19': 'http://purl.obolibrary.org/obo/MONDO_0004867',\n",
-       " 'meddra:10000029': 'http://purl.obolibrary.org/obo/MONDO_0009923',\n",
-       " 'efo:0004286': 'http://purl.obolibrary.org/obo/MONDO_0005399',\n",
-       " 'ncit:c35114': 'http://purl.obolibrary.org/obo/MONDO_0008638',\n",
-       " 'umls:c0275757': 'http://purl.obolibrary.org/obo/MONDO_0000238',\n",
-       " 'efo:0000223': 'http://purl.obolibrary.org/obo/MONDO_0018871',\n",
-       " 'cohd:4278365': 'http://purl.obolibrary.org/obo/MONDO_0018310',\n",
-       " 'sctid:718605009': 'http://purl.obolibrary.org/obo/MONDO_0013993',\n",
-       " 'omim:616977': 'http://purl.obolibrary.org/obo/MONDO_0014858',\n",
-       " 'ncit:c92945': 'http://purl.obolibrary.org/obo/MONDO_0044098',\n",
-       " 'icd10:q87.0': 'http://purl.obolibrary.org/obo/MONDO_0010925',\n",
-       " 'doid:10691': 'http://purl.obolibrary.org/obo/MONDO_0001101',\n",
-       " 'orphanet:1313': 'http://purl.obolibrary.org/obo/MONDO_0008981',\n",
-       " 'icd10:q81.2': 'http://purl.obolibrary.org/obo/MONDO_0019521',\n",
-       " 'orphanet:98619': 'http://purl.obolibrary.org/obo/MONDO_0020207',\n",
-       " 'gard:0012906': 'http://purl.obolibrary.org/obo/MONDO_0012294',\n",
-       " 'umls:c3554105': 'http://purl.obolibrary.org/obo/MONDO_0013972',\n",
-       " 'umls:c0026847': 'http://purl.obolibrary.org/obo/MONDO_0001516',\n",
-       " 'gard:0001220': 'http://purl.obolibrary.org/obo/MONDO_0012588',\n",
-       " 'icd10:q24.8': 'http://purl.obolibrary.org/obo/MONDO_0020454',\n",
-       " 'efo:1000683': 'http://purl.obolibrary.org/obo/MONDO_0001479',\n",
-       " 'umls:cn233170': 'http://purl.obolibrary.org/obo/MONDO_0014708',\n",
-       " 'ncit:c35406': 'http://purl.obolibrary.org/obo/MONDO_0030707',\n",
-       " 'omim:619040': 'http://purl.obolibrary.org/obo/MONDO_0033620',\n",
-       " 'doid:9348': 'http://purl.obolibrary.org/obo/MONDO_0004763',\n",
-       " 'gard:0009535': 'http://purl.obolibrary.org/obo/MONDO_0018768',\n",
-       " 'icd10:e70.3': 'http://purl.obolibrary.org/obo/MONDO_0018137',\n",
-       " 'omim:617069': 'http://purl.obolibrary.org/obo/MONDO_0014898',\n",
-       " 'omim:605627': 'http://purl.obolibrary.org/obo/MONDO_0011575',\n",
-       " 'sctid:126980002': 'http://purl.obolibrary.org/obo/MONDO_0001406',\n",
-       " 'doid:0060326': 'http://purl.obolibrary.org/obo/MONDO_0019773',\n",
-       " 'doid:13574': 'http://purl.obolibrary.org/obo/MONDO_0001749',\n",
-       " 'icd10:g40.4': 'http://purl.obolibrary.org/obo/MONDO_0018614',\n",
-       " 'umls:c3810359': 'http://purl.obolibrary.org/obo/MONDO_0014319',\n",
-       " 'orphanet:231386': 'http://purl.obolibrary.org/obo/MONDO_0016492',\n",
-       " 'icd10:c49.9': 'http://purl.obolibrary.org/obo/MONDO_0012825',\n",
-       " 'orphanet:262896': 'http://purl.obolibrary.org/obo/MONDO_0016959',\n",
-       " 'mesh:c564732': 'http://purl.obolibrary.org/obo/MONDO_0010213',\n",
-       " 'efo:0007359': 'http://purl.obolibrary.org/obo/MONDO_0020334',\n",
-       " 'doid:1647': 'http://purl.obolibrary.org/obo/MONDO_0002067',\n",
-       " 'umls:c1333507': 'http://purl.obolibrary.org/obo/MONDO_0003286',\n",
-       " 'orphanet:3260': 'http://purl.obolibrary.org/obo/MONDO_0011895',\n",
-       " 'mesh:c538240': 'http://purl.obolibrary.org/obo/MONDO_0007094',\n",
-       " 'mesh:c566600': 'http://purl.obolibrary.org/obo/MONDO_0011169',\n",
-       " 'doid:0050994': 'http://purl.obolibrary.org/obo/MONDO_0012982',\n",
-       " 'umls:cn203392': 'http://purl.obolibrary.org/obo/MONDO_0017586',\n",
-       " 'mesh:d007567': 'http://purl.obolibrary.org/obo/MONDO_0006584',\n",
-       " 'umls:c0043388': 'http://purl.obolibrary.org/obo/MONDO_0006019',\n",
-       " 'orphanet:2059': 'http://purl.obolibrary.org/obo/MONDO_0009253',\n",
-       " 'omim:133240': 'http://purl.obolibrary.org/obo/MONDO_0007577',\n",
-       " 'icd10:q51.2': 'http://purl.obolibrary.org/obo/MONDO_0007698',\n",
-       " 'orphanet:77259': 'http://purl.obolibrary.org/obo/MONDO_0009265',\n",
-       " 'umls:c3279790': 'http://purl.obolibrary.org/obo/MONDO_0013567',\n",
-       " 'meddra:10056968': 'http://purl.obolibrary.org/obo/MONDO_0019526',\n",
-       " 'doid:203': 'http://purl.obolibrary.org/obo/MONDO_0002181',\n",
-       " 'ncit:c27937': 'http://purl.obolibrary.org/obo/MONDO_0003534',\n",
-       " 'gtr:an0100478': 'http://purl.obolibrary.org/obo/MONDO_0016910',\n",
-       " 'doid:12803': 'http://purl.obolibrary.org/obo/MONDO_0009662',\n",
-       " 'umls:c1864199': 'http://purl.obolibrary.org/obo/MONDO_0011286',\n",
-       " 'doid:0110816': 'http://purl.obolibrary.org/obo/MONDO_0011803',\n",
-       " 'umls:c0406819': 'http://purl.obolibrary.org/obo/MONDO_0016986',\n",
-       " 'doid:288': 'http://purl.obolibrary.org/obo/MONDO_0010888',\n",
-       " 'umls:c1861536': 'http://purl.obolibrary.org/obo/MONDO_0007339',\n",
-       " 'doid:0111135': 'http://purl.obolibrary.org/obo/MONDO_0012071',\n",
-       " 'mesh:d054319': 'http://purl.obolibrary.org/obo/MONDO_0016370',\n",
-       " 'icd10:d58.8': 'http://purl.obolibrary.org/obo/MONDO_0013042',\n",
-       " 'orphanet:98894': 'http://purl.obolibrary.org/obo/MONDO_0012138',\n",
-       " 'gard:0005461': 'http://purl.obolibrary.org/obo/MONDO_0018801',\n",
-       " 'omim:615181': 'http://purl.obolibrary.org/obo/MONDO_0014071',\n",
-       " 'icd10:q76.3': 'http://purl.obolibrary.org/obo/MONDO_0011018',\n",
-       " 'omim:227400': 'http://purl.obolibrary.org/obo/MONDO_0009210',\n",
-       " 'orphanet:398053': 'http://purl.obolibrary.org/obo/MONDO_0018351',\n",
-       " 'icd10:p61.1': 'http://purl.obolibrary.org/obo/MONDO_0001238',\n",
-       " 'sctid:238118002': 'http://purl.obolibrary.org/obo/MONDO_0006844',\n",
-       " 'orphanet:505242': 'http://purl.obolibrary.org/obo/MONDO_0044726',\n",
-       " 'omim:615625': 'http://purl.obolibrary.org/obo/MONDO_0014282',\n",
-       " 'mesh:c537680': 'http://purl.obolibrary.org/obo/MONDO_0009903',\n",
-       " 'omim:219800': 'http://purl.obolibrary.org/obo/MONDO_0100151',\n",
-       " 'umls:cn187050': 'http://purl.obolibrary.org/obo/MONDO_0008070',\n",
-       " 'orphanet:505237': 'http://purl.obolibrary.org/obo/MONDO_0044319',\n",
-       " 'ncit:c84883': 'http://purl.obolibrary.org/obo/MONDO_0020500',\n",
-       " 'icd10:e71.0': 'http://purl.obolibrary.org/obo/MONDO_0017052',\n",
-       " 'icd10:f82': 'http://purl.obolibrary.org/obo/MONDO_0004922',\n",
-       " 'ncit:c3726': 'http://purl.obolibrary.org/obo/MONDO_0005635',\n",
-       " 'efo:1000281': 'http://purl.obolibrary.org/obo/MONDO_0006232',\n",
-       " 'doid:13461': 'http://purl.obolibrary.org/obo/MONDO_0001721',\n",
-       " 'orphanet:35687': 'http://purl.obolibrary.org/obo/MONDO_0018153',\n",
-       " 'mesh:d016109': 'http://purl.obolibrary.org/obo/MONDO_0017612',\n",
-       " 'icd9:147.8': 'http://purl.obolibrary.org/obo/MONDO_0021315',\n",
-       " 'icd10:e88.81': 'http://purl.obolibrary.org/obo/MONDO_0004955',\n",
-       " 'orphanet:71864': 'http://purl.obolibrary.org/obo/MONDO_0019119',\n",
-       " 'umls:cn206879': 'http://purl.obolibrary.org/obo/MONDO_0019957',\n",
-       " 'mesh:c566101': 'http://purl.obolibrary.org/obo/MONDO_0008506',\n",
-       " 'efo:1001231': 'http://purl.obolibrary.org/obo/MONDO_0020283',\n",
-       " 'umls:c0014805': 'http://purl.obolibrary.org/obo/MONDO_0007571',\n",
-       " 'icd9:083.8': 'http://purl.obolibrary.org/obo/MONDO_0001195',\n",
-       " 'mesh:d018981': 'http://purl.obolibrary.org/obo/MONDO_0015286',\n",
-       " 'omim:613470': 'http://purl.obolibrary.org/obo/MONDO_0013275',\n",
-       " 'icd9:528.79': 'http://purl.obolibrary.org/obo/MONDO_0009237',\n",
-       " 'gard:0001547': 'http://purl.obolibrary.org/obo/MONDO_0022876',\n",
-       " 'icd10:g24.1': 'http://purl.obolibrary.org/obo/MONDO_0012994',\n",
-       " 'mesh:c563209': 'http://purl.obolibrary.org/obo/MONDO_0011093',\n",
-       " 'gard:0001449': 'http://purl.obolibrary.org/obo/MONDO_0022810',\n",
-       " 'orphanet:181441': 'http://purl.obolibrary.org/obo/MONDO_0015906',\n",
-       " 'cohd:440740': 'http://purl.obolibrary.org/obo/MONDO_0017991',\n",
-       " 'orphanet:98962': 'http://purl.obolibrary.org/obo/MONDO_0007377',\n",
-       " 'omim:615328': 'http://purl.obolibrary.org/obo/MONDO_0014131',\n",
-       " 'umls:c1336900': 'http://purl.obolibrary.org/obo/MONDO_0004788',\n",
-       " 'mesh:c564131': 'http://purl.obolibrary.org/obo/MONDO_0013576',\n",
-       " 'orphanet:1261': 'http://purl.obolibrary.org/obo/MONDO_0009167',\n",
-       " 'umls:c0005686': 'http://purl.obolibrary.org/obo/MONDO_0006026',\n",
-       " 'omim:616859': 'http://purl.obolibrary.org/obo/MONDO_0014803',\n",
-       " 'ncit:c3223': 'http://purl.obolibrary.org/obo/MONDO_0017799',\n",
-       " 'icd9:140.5': 'http://purl.obolibrary.org/obo/MONDO_0006834',\n",
-       " 'sctid:3289004': 'http://purl.obolibrary.org/obo/MONDO_0001478',\n",
-       " 'umls:c1861347': 'http://purl.obolibrary.org/obo/MONDO_0008517',\n",
-       " 'icd9:224.2': 'http://purl.obolibrary.org/obo/MONDO_0021488',\n",
-       " 'ncit:c27871': 'http://purl.obolibrary.org/obo/MONDO_0015131',\n",
-       " 'doid:0060459': 'http://purl.obolibrary.org/obo/MONDO_0012761',\n",
-       " 'omim:602079': 'http://purl.obolibrary.org/obo/MONDO_0011182',\n",
-       " 'sctid:12337004': 'http://purl.obolibrary.org/obo/MONDO_0002654',\n",
-       " 'efo:0007296': 'http://purl.obolibrary.org/obo/MONDO_0017879',\n",
-       " 'omim:311300': 'http://purl.obolibrary.org/obo/MONDO_0010704',\n",
-       " 'omim:614190': 'http://purl.obolibrary.org/obo/MONDO_0013616',\n",
-       " 'icd9:136.1': 'http://purl.obolibrary.org/obo/MONDO_0007191',\n",
-       " 'sctid:703523004': 'http://purl.obolibrary.org/obo/MONDO_0011939',\n",
-       " 'icd9:791.0': 'http://purl.obolibrary.org/obo/MONDO_0003634',\n",
-       " 'sctid:723307008': 'http://purl.obolibrary.org/obo/MONDO_0011229',\n",
-       " 'icd10:e77.1': 'http://purl.obolibrary.org/obo/MONDO_0019682',\n",
-       " 'gard:0012860': 'http://purl.obolibrary.org/obo/MONDO_0018996',\n",
-       " 'icdo:8450/0': 'http://purl.obolibrary.org/obo/MONDO_0021091',\n",
-       " 'orphanet:280315': 'http://purl.obolibrary.org/obo/MONDO_0017228',\n",
-       " 'omim:605642': 'http://purl.obolibrary.org/obo/MONDO_0011578',\n",
-       " 'icd9:416.8': 'http://purl.obolibrary.org/obo/MONDO_0009329',\n",
-       " 'sctid:253159001': 'http://purl.obolibrary.org/obo/MONDO_0010011',\n",
-       " 'icd10:i40.9': 'http://purl.obolibrary.org/obo/MONDO_0002815',\n",
-       " 'ncit:c27369': 'http://purl.obolibrary.org/obo/MONDO_0004513',\n",
-       " 'umls:c1836862': 'http://purl.obolibrary.org/obo/MONDO_0012185',\n",
-       " 'sctid:231934009': 'http://purl.obolibrary.org/obo/MONDO_0017392',\n",
-       " 'umls:c4321324': 'http://purl.obolibrary.org/obo/MONDO_0010159',\n",
-       " 'orphanet:183669': 'http://purl.obolibrary.org/obo/MONDO_0015977',\n",
-       " 'icd9:372.2': 'http://purl.obolibrary.org/obo/MONDO_0002307',\n",
-       " 'sctid:51290000': 'http://purl.obolibrary.org/obo/MONDO_0000707',\n",
-       " 'icd10:e79.8': 'http://purl.obolibrary.org/obo/MONDO_0009378',\n",
-       " 'umls:c1333050': 'http://purl.obolibrary.org/obo/MONDO_0004086',\n",
-       " 'doid:6316': 'http://purl.obolibrary.org/obo/MONDO_0003853',\n",
-       " 'mesh:d010381': 'http://purl.obolibrary.org/obo/MONDO_0008214',\n",
-       " 'sctid:191306005': 'http://purl.obolibrary.org/obo/MONDO_0006785',\n",
-       " 'umls:c4082762': 'http://purl.obolibrary.org/obo/MONDO_0001628',\n",
-       " 'mesh:c565431': 'http://purl.obolibrary.org/obo/MONDO_0009536',\n",
-       " 'doid:0050852': 'http://purl.obolibrary.org/obo/MONDO_0000491',\n",
-       " 'gard:0008192': 'http://purl.obolibrary.org/obo/MONDO_0002661',\n",
-       " 'meddra:10069732': 'http://purl.obolibrary.org/obo/MONDO_0015290',\n",
-       " 'gard:0001895': 'http://purl.obolibrary.org/obo/MONDO_0023000',\n",
-       " 'efo:1000029': 'http://purl.obolibrary.org/obo/MONDO_0006034',\n",
-       " 'pmid:3385588': 'http://purl.obolibrary.org/obo/MONDO_0005598',\n",
-       " 'omim:313850': 'http://purl.obolibrary.org/obo/MONDO_0010742',\n",
-       " 'ncit:c27407': 'http://purl.obolibrary.org/obo/MONDO_0004236',\n",
-       " 'mesh:d010489': 'http://purl.obolibrary.org/obo/MONDO_0006898',\n",
-       " 'umls:c1842939': 'http://purl.obolibrary.org/obo/MONDO_0011920',\n",
-       " 'gard:0008642': 'http://purl.obolibrary.org/obo/MONDO_0020541',\n",
-       " 'umls:c0349644': 'http://purl.obolibrary.org/obo/MONDO_0001472',\n",
-       " 'ncit:c6086': 'http://purl.obolibrary.org/obo/MONDO_0006303',\n",
-       " 'icd10:e03.1': 'http://purl.obolibrary.org/obo/MONDO_0020503',\n",
-       " 'ncit:c84607': 'http://purl.obolibrary.org/obo/MONDO_0017215',\n",
-       " 'umls:c4303546': 'http://purl.obolibrary.org/obo/MONDO_0018130',\n",
-       " 'efo:1000718': 'http://purl.obolibrary.org/obo/MONDO_0006564',\n",
-       " 'icd9:995.3': 'http://purl.obolibrary.org/obo/MONDO_0005202',\n",
-       " 'umls:c0005683': 'http://purl.obolibrary.org/obo/MONDO_0006678',\n",
-       " 'sctid:763743003': 'http://purl.obolibrary.org/obo/MONDO_0009524',\n",
-       " 'mesh:c562460': 'http://purl.obolibrary.org/obo/MONDO_0013001',\n",
-       " 'icd9:571.4': 'http://purl.obolibrary.org/obo/MONDO_0002251',\n",
-       " 'mesh:c566487': 'http://purl.obolibrary.org/obo/MONDO_0012392',\n",
-       " 'sctid:39462005': 'http://purl.obolibrary.org/obo/MONDO_0006460',\n",
-       " 'ncit:c5606': 'http://purl.obolibrary.org/obo/MONDO_0002734',\n",
-       " 'omim:137750': 'http://purl.obolibrary.org/obo/MONDO_0007664',\n",
-       " 'icd10:e74.3': 'http://purl.obolibrary.org/obo/MONDO_0017619',\n",
-       " 'icd10:e10': 'http://purl.obolibrary.org/obo/MONDO_0011572',\n",
-       " 'doid:13207': 'http://purl.obolibrary.org/obo/MONDO_0001660',\n",
-       " 'omim:113620': 'http://purl.obolibrary.org/obo/MONDO_0007235',\n",
-       " 'mesh:c566885': 'http://purl.obolibrary.org/obo/MONDO_0009504',\n",
-       " 'umls:c2931777': 'http://purl.obolibrary.org/obo/MONDO_0023288',\n",
-       " 'orphanet:98823': 'http://purl.obolibrary.org/obo/MONDO_0020311',\n",
-       " 'umls:c0266521': 'http://purl.obolibrary.org/obo/MONDO_0007946',\n",
-       " 'mesh:c565153': 'http://purl.obolibrary.org/obo/MONDO_0007385',\n",
-       " 'meddra:10006205': 'http://purl.obolibrary.org/obo/MONDO_0006804',\n",
-       " 'omim:617107': 'http://purl.obolibrary.org/obo/MONDO_0014918',\n",
-       " 'orphanet:139021': 'http://purl.obolibrary.org/obo/MONDO_0015329',\n",
-       " 'orphanet:566231': 'http://purl.obolibrary.org/obo/MONDO_0034216',\n",
-       " 'omim:614291': 'http://purl.obolibrary.org/obo/MONDO_0013669',\n",
-       " 'umls:c1335893': 'http://purl.obolibrary.org/obo/MONDO_0004498',\n",
-       " 'mesh:d018877': 'http://purl.obolibrary.org/obo/MONDO_0005959',\n",
-       " 'icd10:g31.8': 'http://purl.obolibrary.org/obo/MONDO_0010578',\n",
-       " 'mesh:c537402': 'http://purl.obolibrary.org/obo/MONDO_0018061',\n",
-       " 'orphanet:181412': 'http://purl.obolibrary.org/obo/MONDO_0015898',\n",
-       " 'umls:c4011725': 'http://purl.obolibrary.org/obo/MONDO_0008049',\n",
-       " 'ncit:c125592': 'http://purl.obolibrary.org/obo/MONDO_0007732',\n",
-       " 'mesh:c562844': 'http://purl.obolibrary.org/obo/MONDO_0008909',\n",
-       " 'omim:240500': 'http://purl.obolibrary.org/obo/MONDO_0009413',\n",
-       " 'ncit:c7492': 'http://purl.obolibrary.org/obo/MONDO_0006079',\n",
-       " 'orphanet:580572': 'http://purl.obolibrary.org/obo/MONDO_0034987',\n",
-       " 'icd9:228.09': 'http://purl.obolibrary.org/obo/MONDO_0006500',\n",
-       " 'umls:c2746083': 'http://purl.obolibrary.org/obo/MONDO_0013119',\n",
-       " 'umls:c0018817': 'http://purl.obolibrary.org/obo/MONDO_0006664',\n",
-       " 'umls:c1302401': 'http://purl.obolibrary.org/obo/MONDO_0005484',\n",
-       " 'mesh:c563572': 'http://purl.obolibrary.org/obo/MONDO_0008005',\n",
-       " 'umls:c0796173': 'http://purl.obolibrary.org/obo/MONDO_0010078',\n",
-       " 'umls:c1334615': 'http://purl.obolibrary.org/obo/MONDO_0000994',\n",
-       " 'sctid:32139003': 'http://purl.obolibrary.org/obo/MONDO_0022745',\n",
-       " 'sctid:186273003': 'http://purl.obolibrary.org/obo/MONDO_0005787',\n",
-       " 'cohd:4181333': 'http://purl.obolibrary.org/obo/MONDO_0004669',\n",
-       " 'umls:cn203566': 'http://purl.obolibrary.org/obo/MONDO_0017679',\n",
-       " 'icd10:j69.0': 'http://purl.obolibrary.org/obo/MONDO_0000265',\n",
-       " 'sctid:722114007': 'http://purl.obolibrary.org/obo/MONDO_0012387',\n",
-       " 'umls:c0267095': 'http://purl.obolibrary.org/obo/MONDO_0004697',\n",
-       " 'gard:0008276': 'http://purl.obolibrary.org/obo/MONDO_0010217',\n",
-       " 'gard:0013221': 'http://purl.obolibrary.org/obo/MONDO_0010656',\n",
-       " 'mesh:d056725': 'http://purl.obolibrary.org/obo/MONDO_0008668',\n",
-       " 'icd10:l43.1': 'http://purl.obolibrary.org/obo/MONDO_0018056',\n",
-       " 'umls:c0154271': 'http://purl.obolibrary.org/obo/MONDO_0021162',\n",
-       " 'omim:233270': 'http://purl.obolibrary.org/obo/MONDO_0009298',\n",
-       " 'icd9:376.13': 'http://purl.obolibrary.org/obo/MONDO_0005135',\n",
-       " 'doid:0050050': 'http://purl.obolibrary.org/obo/MONDO_0000233',\n",
-       " 'sctid:717041008': 'http://purl.obolibrary.org/obo/MONDO_0017264',\n",
-       " 'sctid:61599003': 'http://purl.obolibrary.org/obo/MONDO_0004625',\n",
-       " 'icd10:j84.841': 'http://purl.obolibrary.org/obo/MONDO_0016322',\n",
-       " 'omim:300578': 'http://purl.obolibrary.org/obo/MONDO_0010364',\n",
-       " 'umls:c1859721': 'http://purl.obolibrary.org/obo/MONDO_0008823',\n",
-       " 'icd9:777.5': 'http://purl.obolibrary.org/obo/MONDO_0005313',\n",
-       " 'orphanet:2409': 'http://purl.obolibrary.org/obo/MONDO_0010851',\n",
-       " 'umls:c0014733': 'http://purl.obolibrary.org/obo/MONDO_0001266',\n",
-       " 'icd9:410.60': 'http://purl.obolibrary.org/obo/MONDO_0004780',\n",
-       " 'icd10:g70.0': 'http://purl.obolibrary.org/obo/MONDO_0009688',\n",
-       " 'omim:211980': 'http://purl.obolibrary.org/obo/MONDO_0008903',\n",
-       " 'orphanet:231111': 'http://purl.obolibrary.org/obo/MONDO_0016474',\n",
-       " 'efo:0007526': 'http://purl.obolibrary.org/obo/MONDO_0005998',\n",
-       " 'umls:c0268136': 'http://purl.obolibrary.org/obo/MONDO_0012531',\n",
-       " 'mesh:d012507': 'http://purl.obolibrary.org/obo/MONDO_0019338',\n",
-       " 'umls:c0036916': 'http://purl.obolibrary.org/obo/MONDO_0021681',\n",
-       " 'umls:c2931684': 'http://purl.obolibrary.org/obo/MONDO_0019109',\n",
-       " 'mesh:c565649': 'http://purl.obolibrary.org/obo/MONDO_0009077',\n",
-       " 'efo:1001762': 'http://purl.obolibrary.org/obo/MONDO_0043975',\n",
-       " 'gard:0008730': 'http://purl.obolibrary.org/obo/MONDO_0005977',\n",
-       " 'mesh:c535662': 'http://purl.obolibrary.org/obo/MONDO_0007055',\n",
-       " 'mesh:d007970': 'http://purl.obolibrary.org/obo/MONDO_0003785',\n",
-       " 'omim:616486': 'http://purl.obolibrary.org/obo/MONDO_0014660',\n",
-       " 'sctid:312898002': 'http://purl.obolibrary.org/obo/MONDO_0015807',\n",
-       " 'sctid:277637000': 'http://purl.obolibrary.org/obo/MONDO_0020325',\n",
-       " 'mesh:d017681': 'http://purl.obolibrary.org/obo/MONDO_0015691',\n",
-       " 'icd10:e24': 'http://purl.obolibrary.org/obo/MONDO_0018912',\n",
-       " 'doid:14392': 'http://purl.obolibrary.org/obo/MONDO_0001954',\n",
-       " 'gard:0003945': 'http://purl.obolibrary.org/obo/MONDO_0012184',\n",
-       " 'icd9:205.90': 'http://purl.obolibrary.org/obo/MONDO_0004643',\n",
-       " 'omim:228930': 'http://purl.obolibrary.org/obo/MONDO_0009232',\n",
-       " 'orphanet:276241': 'http://purl.obolibrary.org/obo/MONDO_0017175',\n",
-       " 'sctid:44054006': 'http://purl.obolibrary.org/obo/MONDO_0005148',\n",
-       " 'icd9:238.79': 'http://purl.obolibrary.org/obo/MONDO_0010627',\n",
-       " 'meddra:10028095': 'http://purl.obolibrary.org/obo/MONDO_0018938',\n",
-       " 'icd10:e75.2': 'http://purl.obolibrary.org/obo/MONDO_0015349',\n",
-       " 'icd10:r37': 'http://purl.obolibrary.org/obo/MONDO_0002134',\n",
-       " 'ncit:c53970': 'http://purl.obolibrary.org/obo/MONDO_0011806',\n",
-       " 'umls:c1384666': 'http://purl.obolibrary.org/obo/MONDO_0005365',\n",
-       " 'gard:0008604': 'http://purl.obolibrary.org/obo/MONDO_0002093',\n",
-       " 'umls:c1334587': 'http://purl.obolibrary.org/obo/MONDO_0003828',\n",
-       " 'sctid:424440001': 'http://purl.obolibrary.org/obo/MONDO_0003198',\n",
-       " 'gard:0012641': 'http://purl.obolibrary.org/obo/MONDO_0013268',\n",
-       " 'hp:0005696': 'http://purl.obolibrary.org/obo/MONDO_0019673',\n",
-       " 'efo:0007174': 'http://purl.obolibrary.org/obo/MONDO_0005672',\n",
-       " 'doid:1634': 'http://purl.obolibrary.org/obo/MONDO_0002063',\n",
-       " 'omim:278760': 'http://purl.obolibrary.org/obo/MONDO_0010215',\n",
-       " 'gard:0003450': 'http://purl.obolibrary.org/obo/MONDO_0043127',\n",
-       " 'orphanet:79333': 'http://purl.obolibrary.org/obo/MONDO_0012118',\n",
-       " 'omim:615637': 'http://purl.obolibrary.org/obo/MONDO_0014289',\n",
-       " 'umls:c2677105': 'http://purl.obolibrary.org/obo/MONDO_0012829',\n",
-       " 'gard:0005614': 'http://purl.obolibrary.org/obo/MONDO_0010317',\n",
-       " 'doid:5501': 'http://purl.obolibrary.org/obo/MONDO_0003471',\n",
-       " 'umls:c2931410': 'http://purl.obolibrary.org/obo/MONDO_0023165',\n",
-       " 'orphanet:98026': 'http://purl.obolibrary.org/obo/MONDO_0020013',\n",
-       " 'umls:c1846719': 'http://purl.obolibrary.org/obo/MONDO_0011780',\n",
-       " 'efo:0005950': 'http://purl.obolibrary.org/obo/MONDO_0005586',\n",
-       " 'umls:c0268837': 'http://purl.obolibrary.org/obo/MONDO_0002287',\n",
-       " 'mesh:d010267': 'http://purl.obolibrary.org/obo/MONDO_0006592',\n",
-       " 'orphanet:93443': 'http://purl.obolibrary.org/obo/MONDO_0019702',\n",
-       " 'icd9:455.6': 'http://purl.obolibrary.org/obo/MONDO_0004873',\n",
-       " 'efo:0004144': 'http://purl.obolibrary.org/obo/MONDO_0013571',\n",
-       " 'gard:0007650': 'http://purl.obolibrary.org/obo/MONDO_0056819',\n",
-       " 'mesh:d011654': 'http://purl.obolibrary.org/obo/MONDO_0006932',\n",
-       " 'sctid:764696007': 'http://purl.obolibrary.org/obo/MONDO_0016839',\n",
-       " 'omim:206400': 'http://purl.obolibrary.org/obo/MONDO_0008790',\n",
-       " 'meddra:10050185': 'http://purl.obolibrary.org/obo/MONDO_0015597',\n",
-       " 'ncit:c3047': 'http://purl.obolibrary.org/obo/MONDO_0043606',\n",
-       " 'orphanet:58017': 'http://purl.obolibrary.org/obo/MONDO_0018935',\n",
-       " 'icd10:q92.2': 'http://purl.obolibrary.org/obo/MONDO_0015767',\n",
-       " 'ncit:c26869': 'http://purl.obolibrary.org/obo/MONDO_0002771',\n",
-       " 'umls:c3280415': 'http://purl.obolibrary.org/obo/MONDO_0013680',\n",
-       " 'omim:613464': 'http://purl.obolibrary.org/obo/MONDO_0013274',\n",
-       " 'umls:c1332994': 'http://purl.obolibrary.org/obo/MONDO_0003654',\n",
-       " 'icd9:428.3': 'http://purl.obolibrary.org/obo/MONDO_0006727',\n",
-       " 'umls:c0375381': 'http://purl.obolibrary.org/obo/MONDO_0001721',\n",
-       " 'ncit:c50486': 'http://purl.obolibrary.org/obo/MONDO_0002679',\n",
-       " 'orphanet:329466': 'http://purl.obolibrary.org/obo/MONDO_0014033',\n",
-       " 'icd10:m30.0': 'http://purl.obolibrary.org/obo/MONDO_0018595',\n",
-       " 'sctid:721973006': 'http://purl.obolibrary.org/obo/MONDO_0011976',\n",
-       " 'mesh:c563120': 'http://purl.obolibrary.org/obo/MONDO_0009885',\n",
-       " 'icd10:q33.6': 'http://purl.obolibrary.org/obo/MONDO_0009936',\n",
-       " 'umls:c4510411': 'http://purl.obolibrary.org/obo/MONDO_0100211',\n",
-       " 'mesh:d007039': 'http://purl.obolibrary.org/obo/MONDO_0003037',\n",
-       " 'icd10:e88.1': 'http://purl.obolibrary.org/obo/MONDO_0019555',\n",
-       " 'doid:13099': 'http://purl.obolibrary.org/obo/MONDO_0016820',\n",
-       " 'ncit:c34564': 'http://purl.obolibrary.org/obo/MONDO_0044811',\n",
-       " 'ncit:c128048': 'http://purl.obolibrary.org/obo/MONDO_0016282',\n",
-       " 'umls:c0078981': 'http://purl.obolibrary.org/obo/MONDO_0008813',\n",
-       " 'sctid:717915004': 'http://purl.obolibrary.org/obo/MONDO_0007202',\n",
-       " 'orphanet:2892': 'http://purl.obolibrary.org/obo/MONDO_0009873',\n",
-       " 'doid:1025': 'http://purl.obolibrary.org/obo/MONDO_0005126',\n",
-       " 'icd10:f45.0': 'http://purl.obolibrary.org/obo/MONDO_0003117',\n",
-       " 'orphanet:3041': 'http://purl.obolibrary.org/obo/MONDO_0010505',\n",
-       " 'doid:8912': 'http://purl.obolibrary.org/obo/MONDO_0004677',\n",
-       " 'doid:6312': 'http://purl.obolibrary.org/obo/MONDO_0003849',\n",
-       " 'orphanet:284993': 'http://purl.obolibrary.org/obo/MONDO_0017310',\n",
-       " 'efo:0000614': 'http://purl.obolibrary.org/obo/MONDO_0016158',\n",
-       " 'omim:190410': 'http://purl.obolibrary.org/obo/MONDO_0008600',\n",
-       " 'sctid:82077006': 'http://purl.obolibrary.org/obo/MONDO_0018947',\n",
-       " 'mesh:c537321': 'http://purl.obolibrary.org/obo/MONDO_0009616',\n",
-       " 'ncit:c34590': 'http://purl.obolibrary.org/obo/MONDO_0001615',\n",
-       " 'umls:c0085696': 'http://purl.obolibrary.org/obo/MONDO_0022103',\n",
-       " 'umls:c0279701': 'http://purl.obolibrary.org/obo/MONDO_0004304',\n",
-       " 'mesh:d053653': 'http://purl.obolibrary.org/obo/MONDO_0021746',\n",
-       " 'gard:0002952': 'http://purl.obolibrary.org/obo/MONDO_0100213',\n",
-       " 'icdo:8334/0': 'http://purl.obolibrary.org/obo/MONDO_0004558',\n",
-       " 'efo:0003094': 'http://purl.obolibrary.org/obo/MONDO_0016733',\n",
-       " 'icd9:270.8': 'http://purl.obolibrary.org/obo/MONDO_0009825',\n",
-       " 'ncit:c114541': 'http://purl.obolibrary.org/obo/MONDO_0002142',\n",
-       " 'icd9:759.89': 'http://purl.obolibrary.org/obo/MONDO_0007604',\n",
-       " 'doid:0050382': 'http://purl.obolibrary.org/obo/MONDO_0000320',\n",
-       " 'icd9:011.73': 'http://purl.obolibrary.org/obo/MONDO_0004813',\n",
-       " 'icd10:f65.52': 'http://purl.obolibrary.org/obo/MONDO_0001132',\n",
-       " 'umls:cn205933': 'http://purl.obolibrary.org/obo/MONDO_0019299',\n",
-       " 'sctid:118617000': 'http://purl.obolibrary.org/obo/MONDO_0007243',\n",
-       " 'mesh:c538103': 'http://purl.obolibrary.org/obo/MONDO_0001332',\n",
-       " 'gtr:an1355639': 'http://purl.obolibrary.org/obo/MONDO_0020773',\n",
-       " 'icd10:j67.8': 'http://purl.obolibrary.org/obo/MONDO_0017853',\n",
-       " 'meddra:10070535': 'http://purl.obolibrary.org/obo/MONDO_0007315',\n",
-       " 'umls:c1837893': 'http://purl.obolibrary.org/obo/MONDO_0012054',\n",
-       " 'ncit:c6175': 'http://purl.obolibrary.org/obo/MONDO_0001977',\n",
-       " 'orphanet:712': 'http://purl.obolibrary.org/obo/MONDO_0013275',\n",
-       " 'icd10:q32.1': 'http://purl.obolibrary.org/obo/MONDO_0016060',\n",
-       " 'icd10:l12.2': 'http://purl.obolibrary.org/obo/MONDO_0006565',\n",
-       " 'ncit:c3467': 'http://purl.obolibrary.org/obo/MONDO_0000607',\n",
-       " 'gard:0002365': 'http://purl.obolibrary.org/obo/MONDO_0017200',\n",
-       " 'umls:c1846796': 'http://purl.obolibrary.org/obo/MONDO_0011773',\n",
-       " 'sctid:31788005': 'http://purl.obolibrary.org/obo/MONDO_0001918',\n",
-       " 'icd10:h90.3': 'http://purl.obolibrary.org/obo/MONDO_0011673',\n",
-       " 'umls:cn200062': 'http://purl.obolibrary.org/obo/MONDO_0015651',\n",
-       " 'mesh:d008339': 'http://purl.obolibrary.org/obo/MONDO_0005837',\n",
-       " 'umls:c1852718': 'http://purl.obolibrary.org/obo/MONDO_0007360',\n",
-       " 'gard:0001358': 'http://purl.obolibrary.org/obo/MONDO_0009671',\n",
-       " 'cohd:435315': 'http://purl.obolibrary.org/obo/MONDO_0008541',\n",
-       " 'ncit:c3174': 'http://purl.obolibrary.org/obo/MONDO_0011996',\n",
-       " 'orphanet:295132': 'http://purl.obolibrary.org/obo/MONDO_0017517',\n",
-       " 'icd10:g23.0': 'http://purl.obolibrary.org/obo/MONDO_0017999',\n",
-       " 'sctid:11282001': 'http://purl.obolibrary.org/obo/MONDO_0004737',\n",
-       " 'ncit:c135090': 'http://purl.obolibrary.org/obo/MONDO_0015064',\n",
-       " 'gard:0004905': 'http://purl.obolibrary.org/obo/MONDO_0010039',\n",
-       " 'icd10:k76.7': 'http://purl.obolibrary.org/obo/MONDO_0001382',\n",
-       " 'ncit:c5744': 'http://purl.obolibrary.org/obo/MONDO_0004067',\n",
-       " 'sctid:702376003': 'http://purl.obolibrary.org/obo/MONDO_0015548',\n",
-       " 'icd10:k40.90': 'http://purl.obolibrary.org/obo/MONDO_0000746',\n",
-       " 'gard:0009192': 'http://purl.obolibrary.org/obo/MONDO_0010949',\n",
-       " 'mesh:d000789': 'http://purl.obolibrary.org/obo/MONDO_0006805',\n",
-       " 'ncit:c125599': 'http://purl.obolibrary.org/obo/MONDO_0010193',\n",
-       " 'umls:c0949595': 'http://purl.obolibrary.org/obo/MONDO_0009299',\n",
-       " 'umls:c2930890': 'http://purl.obolibrary.org/obo/MONDO_0011273',\n",
-       " 'hp:0002592': 'http://purl.obolibrary.org/obo/MONDO_0001126',\n",
-       " 'omim:231530': 'http://purl.obolibrary.org/obo/MONDO_0017715',\n",
-       " 'cohd:438851': 'http://purl.obolibrary.org/obo/MONDO_0017985',\n",
-       " 'icd10:g10': 'http://purl.obolibrary.org/obo/MONDO_0007739',\n",
-       " 'sctid:230407006': 'http://purl.obolibrary.org/obo/MONDO_0019485',\n",
-       " 'omim:614924': 'http://purl.obolibrary.org/obo/MONDO_0013971',\n",
-       " 'umls:c0271084': 'http://purl.obolibrary.org/obo/MONDO_0005417',\n",
-       " 'umls:cn203291': 'http://purl.obolibrary.org/obo/MONDO_0017555',\n",
-       " 'umls:c4014676': 'http://purl.obolibrary.org/obo/MONDO_0014399',\n",
-       " 'icd10:e83.8': 'http://purl.obolibrary.org/obo/MONDO_0017766',\n",
-       " 'icd10:q21.1': 'http://purl.obolibrary.org/obo/MONDO_0019831',\n",
-       " 'sctid:400080004': 'http://purl.obolibrary.org/obo/MONDO_0006602',\n",
-       " 'icd10:q96.8': 'http://purl.obolibrary.org/obo/MONDO_0019499',\n",
-       " 'umls:c1843832': 'http://purl.obolibrary.org/obo/MONDO_0011837',\n",
-       " 'doid:1760': 'http://purl.obolibrary.org/obo/MONDO_0002101',\n",
-       " 'omim:271530': 'http://purl.obolibrary.org/obo/MONDO_0010070',\n",
-       " 'doid:7642': 'http://purl.obolibrary.org/obo/MONDO_0004315',\n",
-       " 'icd10:d57.20': 'http://purl.obolibrary.org/obo/MONDO_0011382',\n",
-       " 'orphanet:2186': 'http://purl.obolibrary.org/obo/MONDO_0016350',\n",
-       " 'icd9:086.5': 'http://purl.obolibrary.org/obo/MONDO_0005459',\n",
-       " 'icd9:376.35': 'http://purl.obolibrary.org/obo/MONDO_0001513',\n",
-       " 'doid:1517': 'http://purl.obolibrary.org/obo/MONDO_0005694',\n",
-       " 'omim:109100': 'http://purl.obolibrary.org/obo/MONDO_0007179',\n",
-       " 'umls:c0005592': 'http://purl.obolibrary.org/obo/MONDO_0005668',\n",
-       " 'mesh:c566235': 'http://purl.obolibrary.org/obo/MONDO_0007178',\n",
-       " 'ncit:c39954': 'http://purl.obolibrary.org/obo/MONDO_0024235',\n",
-       " 'cohd:199881': 'http://purl.obolibrary.org/obo/MONDO_0006337',\n",
-       " 'orphanet:466921': 'http://purl.obolibrary.org/obo/MONDO_0018759',\n",
-       " 'orphanet:85128': 'http://purl.obolibrary.org/obo/MONDO_0011838',\n",
-       " 'sctid:416956002': 'http://purl.obolibrary.org/obo/MONDO_0024280',\n",
-       " 'doid:1642': 'http://purl.obolibrary.org/obo/MONDO_0002066',\n",
-       " 'omim:209050': 'http://purl.obolibrary.org/obo/MONDO_0008844',\n",
-       " 'umls:c1865133': 'http://purl.obolibrary.org/obo/MONDO_0011262',\n",
-       " 'orphanet:369886': 'http://purl.obolibrary.org/obo/MONDO_0018246',\n",
-       " 'mesh:c562718': 'http://purl.obolibrary.org/obo/MONDO_0012747',\n",
-       " 'umls:c0220769': 'http://purl.obolibrary.org/obo/MONDO_0002010',\n",
-       " 'icd9:258.02': 'http://purl.obolibrary.org/obo/MONDO_0019003',\n",
-       " 'icd10:a86': 'http://purl.obolibrary.org/obo/MONDO_0015595',\n",
-       " 'mesh:d000745': 'http://purl.obolibrary.org/obo/MONDO_0003689',\n",
-       " 'mesh:c564906': 'http://purl.obolibrary.org/obo/MONDO_0009836',\n",
-       " 'umls:c2750737': 'http://purl.obolibrary.org/obo/MONDO_0013184',\n",
-       " 'mesh:c566472': 'http://purl.obolibrary.org/obo/MONDO_0012505',\n",
-       " 'omim:618067': 'http://purl.obolibrary.org/obo/MONDO_0054845',\n",
-       " 'mesh:d014352': 'http://purl.obolibrary.org/obo/MONDO_0000940',\n",
-       " 'sctid:266238009': 'http://purl.obolibrary.org/obo/MONDO_0001113',\n",
-       " 'orphanet:498345': 'http://purl.obolibrary.org/obo/MONDO_0028737',\n",
-       " 'icd10:e71.1': 'http://purl.obolibrary.org/obo/MONDO_0008760',\n",
-       " 'orphanet:181387': 'http://purl.obolibrary.org/obo/MONDO_0015890',\n",
-       " 'mesh:d006373': 'http://purl.obolibrary.org/obo/MONDO_0004664',\n",
-       " 'sctid:416878008': 'http://purl.obolibrary.org/obo/MONDO_0002309',\n",
-       " 'sctid:125586008': 'http://purl.obolibrary.org/obo/MONDO_0005917',\n",
-       " 'umls:c1833564': 'http://purl.obolibrary.org/obo/MONDO_0010905',\n",
-       " 'icd10:c37': 'http://purl.obolibrary.org/obo/MONDO_0016974',\n",
-       " 'umls:c0266725': 'http://purl.obolibrary.org/obo/MONDO_0015386',\n",
-       " 'omim:609289': 'http://purl.obolibrary.org/obo/MONDO_0012242',\n",
-       " 'sctid:12045002': 'http://purl.obolibrary.org/obo/MONDO_0010157',\n",
-       " 'cohd:4307885': 'http://purl.obolibrary.org/obo/MONDO_0019499',\n",
-       " 'mesh:c562465': 'http://purl.obolibrary.org/obo/MONDO_0012000',\n",
-       " 'omim:121820': 'http://purl.obolibrary.org/obo/MONDO_0007375',\n",
-       " 'mesh:c535613': 'http://purl.obolibrary.org/obo/MONDO_0017315',\n",
-       " 'mesh:c537440': 'http://purl.obolibrary.org/obo/MONDO_0018870',\n",
-       " 'icd10:q01.2': 'http://purl.obolibrary.org/obo/MONDO_0017079',\n",
-       " 'omim:609958': 'http://purl.obolibrary.org/obo/MONDO_0012379',\n",
-       " 'orphanet:3314': 'http://purl.obolibrary.org/obo/MONDO_0008142',\n",
-       " 'umls:cn030358': 'http://purl.obolibrary.org/obo/MONDO_0009143',\n",
-       " 'umls:c1335100': 'http://purl.obolibrary.org/obo/MONDO_0003904',\n",
-       " 'gard:0009690': 'http://purl.obolibrary.org/obo/MONDO_0019967',\n",
-       " 'umls:c4518537': 'http://purl.obolibrary.org/obo/MONDO_0010878',\n",
-       " 'orphanet:324575': 'http://purl.obolibrary.org/obo/MONDO_0017935',\n",
-       " 'doid:0110790': 'http://purl.obolibrary.org/obo/MONDO_0012787',\n",
-       " 'umls:c2930955': 'http://purl.obolibrary.org/obo/MONDO_0008809',\n",
-       " 'mesh:d001162': 'http://purl.obolibrary.org/obo/MONDO_0006659',\n",
-       " 'mesh:d000361': 'http://purl.obolibrary.org/obo/MONDO_0015977',\n",
-       " 'gard:0003040': 'http://purl.obolibrary.org/obo/MONDO_0023510',\n",
-       " 'mesh:c564866': 'http://purl.obolibrary.org/obo/MONDO_0009920',\n",
-       " 'icd9:529.5': 'http://purl.obolibrary.org/obo/MONDO_0007655',\n",
-       " 'omim:612921': 'http://purl.obolibrary.org/obo/MONDO_0013039',\n",
-       " 'doid:630': 'http://purl.obolibrary.org/obo/MONDO_0003847',\n",
-       " 'orphanet:96191': 'http://purl.obolibrary.org/obo/MONDO_0019921',\n",
-       " 'sctid:253899000': 'http://purl.obolibrary.org/obo/MONDO_0018553',\n",
-       " 'sctid:763314009': 'http://purl.obolibrary.org/obo/MONDO_0018281',\n",
-       " 'icd10:l81.8': 'http://purl.obolibrary.org/obo/MONDO_0017239',\n",
-       " 'sctid:254100000': 'http://purl.obolibrary.org/obo/MONDO_0019675',\n",
-       " 'omim:603830': 'http://purl.obolibrary.org/obo/MONDO_0011377',\n",
-       " 'mesh:c565605': 'http://purl.obolibrary.org/obo/MONDO_0009149',\n",
-       " 'efo:1001387': 'http://purl.obolibrary.org/obo/MONDO_0004717',\n",
-       " 'icd10:q56': 'http://purl.obolibrary.org/obo/MONDO_0024665',\n",
-       " 'icd9:756.59': 'http://purl.obolibrary.org/obo/MONDO_0009274',\n",
-       " 'umls:c1864815': 'http://purl.obolibrary.org/obo/MONDO_0012327',\n",
-       " 'icd10:e22.0': 'http://purl.obolibrary.org/obo/MONDO_0017581',\n",
-       " 'icd10:c96.4': 'http://purl.obolibrary.org/obo/MONDO_0009975',\n",
-       " 'mesh:d012481': 'http://purl.obolibrary.org/obo/MONDO_0024982',\n",
-       " 'icd9:172.0': 'http://purl.obolibrary.org/obo/MONDO_0005012',\n",
-       " 'icd9:273.2': 'http://purl.obolibrary.org/obo/MONDO_0005576',\n",
-       " 'omimps:224050': 'http://purl.obolibrary.org/obo/MONDO_0009133',\n",
-       " 'gard:0010272': 'http://purl.obolibrary.org/obo/MONDO_0012641',\n",
-       " 'omim:249000': 'http://purl.obolibrary.org/obo/MONDO_0009571',\n",
-       " 'omim:616263': 'http://purl.obolibrary.org/obo/MONDO_0014554',\n",
-       " 'icd9:377.31': 'http://purl.obolibrary.org/obo/MONDO_0006879',\n",
-       " 'gard:0000693': 'http://purl.obolibrary.org/obo/MONDO_0015200',\n",
-       " 'mesh:c536639': 'http://purl.obolibrary.org/obo/MONDO_0042913',\n",
-       " 'ncit:c98987': 'http://purl.obolibrary.org/obo/MONDO_0009628',\n",
-       " 'omim:617460': 'http://purl.obolibrary.org/obo/MONDO_0044320',\n",
-       " 'efo:0006951': 'http://purl.obolibrary.org/obo/MONDO_0001874',\n",
-       " 'sctid:717010007': 'http://purl.obolibrary.org/obo/MONDO_0011633',\n",
-       " 'umls:c2987239': 'http://purl.obolibrary.org/obo/MONDO_0006347',\n",
-       " 'hp:0032154': 'http://purl.obolibrary.org/obo/MONDO_0005318',\n",
-       " 'omim:616373': 'http://purl.obolibrary.org/obo/MONDO_0014613',\n",
-       " 'sctid:719020006': 'http://purl.obolibrary.org/obo/MONDO_0010708',\n",
-       " 'sctid:19885005': 'http://purl.obolibrary.org/obo/MONDO_0001511',\n",
-       " 'umls:c0432194': 'http://purl.obolibrary.org/obo/MONDO_0010013',\n",
-       " 'icd9:596.52': 'http://purl.obolibrary.org/obo/MONDO_0001446',\n",
-       " 'icd9:521.4': 'http://purl.obolibrary.org/obo/MONDO_0001670',\n",
-       " 'doid:4203': 'http://purl.obolibrary.org/obo/MONDO_0002912',\n",
-       " 'icd10:h31.12': 'http://purl.obolibrary.org/obo/MONDO_0004886',\n",
-       " 'sctid:47017007': 'http://purl.obolibrary.org/obo/MONDO_0015430',\n",
-       " 'meddra:10006500': 'http://purl.obolibrary.org/obo/MONDO_0005683',\n",
-       " 'umls:cn225942': 'http://purl.obolibrary.org/obo/MONDO_0018341',\n",
-       " 'orphanet:309803': 'http://purl.obolibrary.org/obo/MONDO_0010823',\n",
-       " 'umls:cn203333': 'http://purl.obolibrary.org/obo/MONDO_0009855',\n",
-       " 'doid:0110973': 'http://purl.obolibrary.org/obo/MONDO_0010538',\n",
-       " 'meddra:10030286': 'http://purl.obolibrary.org/obo/MONDO_0016695',\n",
-       " 'mesh:c537488': 'http://purl.obolibrary.org/obo/MONDO_0007383',\n",
-       " 'icd10:b25.8': 'http://purl.obolibrary.org/obo/MONDO_0018108',\n",
-       " 'icd10:q13.1': 'http://purl.obolibrary.org/obo/MONDO_0007119',\n",
-       " 'doid:0050696': 'http://purl.obolibrary.org/obo/MONDO_0000408',\n",
-       " 'doid:0070157': 'http://purl.obolibrary.org/obo/MONDO_0013337',\n",
-       " 'icd10:c00.d48': 'http://purl.obolibrary.org/obo/MONDO_0005070',\n",
-       " 'doid:0060132': 'http://purl.obolibrary.org/obo/MONDO_0000662',\n",
-       " 'icd10:d57.1': 'http://purl.obolibrary.org/obo/MONDO_0017146',\n",
-       " 'sctid:200936003': 'http://purl.obolibrary.org/obo/MONDO_0004670',\n",
-       " 'icd10:g11.8': 'http://purl.obolibrary.org/obo/MONDO_0010064',\n",
-       " 'umls:cn202725': 'http://purl.obolibrary.org/obo/MONDO_0017234',\n",
-       " 'icd10:c86.5': 'http://purl.obolibrary.org/obo/MONDO_0004977',\n",
-       " 'orphanet:401979': 'http://purl.obolibrary.org/obo/MONDO_0013223',\n",
-       " 'ncit:c4646': 'http://purl.obolibrary.org/obo/MONDO_0020550',\n",
-       " 'omim:617875': 'http://purl.obolibrary.org/obo/MONDO_0044327',\n",
-       " 'sctid:312925009': 'http://purl.obolibrary.org/obo/MONDO_0007630',\n",
-       " 'umls:c4540488': 'http://purl.obolibrary.org/obo/MONDO_0054716',\n",
-       " 'umls:c0024473': 'http://purl.obolibrary.org/obo/MONDO_0006844',\n",
-       " 'ncit:c35139': 'http://purl.obolibrary.org/obo/MONDO_0007318',\n",
-       " 'umls:cn239392': 'http://purl.obolibrary.org/obo/MONDO_0000608',\n",
-       " 'mesh:c537135': 'http://purl.obolibrary.org/obo/MONDO_0010277',\n",
-       " 'doid:0110794': 'http://purl.obolibrary.org/obo/MONDO_0012928',\n",
-       " 'omim:609628': 'http://purl.obolibrary.org/obo/MONDO_0012316',\n",
-       " 'gard:0008344': 'http://purl.obolibrary.org/obo/MONDO_0008306',\n",
-       " 'gard:0004475': 'http://purl.obolibrary.org/obo/MONDO_0008303',\n",
-       " 'gard:0003505': 'http://purl.obolibrary.org/obo/MONDO_0014035',\n",
-       " 'umls:cn206727': 'http://purl.obolibrary.org/obo/MONDO_0019784',\n",
-       " 'omim:175200': 'http://purl.obolibrary.org/obo/MONDO_0008280',\n",
-       " 'umls:c1865208': 'http://purl.obolibrary.org/obo/MONDO_0011248',\n",
-       " 'gard:0001072': 'http://purl.obolibrary.org/obo/MONDO_0007542',\n",
-       " 'gard:0002572': 'http://purl.obolibrary.org/obo/MONDO_0007688',\n",
-       " 'sctid:238757003': 'http://purl.obolibrary.org/obo/MONDO_0023679',\n",
-       " 'omim:133000': 'http://purl.obolibrary.org/obo/MONDO_0007570',\n",
-       " 'icd10:f68.11': 'http://purl.obolibrary.org/obo/MONDO_0002103',\n",
-       " 'doid:14145': 'http://purl.obolibrary.org/obo/MONDO_0006081',\n",
-       " 'ncit:c35685': 'http://purl.obolibrary.org/obo/MONDO_0015808',\n",
-       " 'ncit:c3049': 'http://purl.obolibrary.org/obo/MONDO_0005033',\n",
-       " 'doid:0110081': 'http://purl.obolibrary.org/obo/MONDO_0012434',\n",
-       " 'icd9:230.2': 'http://purl.obolibrary.org/obo/MONDO_0004950',\n",
-       " 'omim:613252': 'http://purl.obolibrary.org/obo/MONDO_0013198',\n",
-       " 'orphanet:946': 'http://purl.obolibrary.org/obo/MONDO_0019796',\n",
-       " 'umls:cn244564': 'http://purl.obolibrary.org/obo/MONDO_0033482',\n",
-       " 'icd10:c34.1': 'http://purl.obolibrary.org/obo/MONDO_0005933',\n",
-       " 'doid:5505': 'http://purl.obolibrary.org/obo/MONDO_0003475',\n",
-       " 'orphanet:183': 'http://purl.obolibrary.org/obo/MONDO_0015943',\n",
-       " 'sctid:66489009': 'http://purl.obolibrary.org/obo/MONDO_0020110',\n",
-       " 'icd10:r77.0': 'http://purl.obolibrary.org/obo/MONDO_0014449',\n",
-       " 'umls:c0014493': 'http://purl.obolibrary.org/obo/MONDO_0001615',\n",
-       " 'orphanet:238459': 'http://purl.obolibrary.org/obo/MONDO_0011342',\n",
-       " 'orphanet:352675': 'http://purl.obolibrary.org/obo/MONDO_0010479',\n",
-       " 'doid:0110496': 'http://purl.obolibrary.org/obo/MONDO_0011991',\n",
-       " 'icd10:c03.0': 'http://purl.obolibrary.org/obo/MONDO_0004615',\n",
-       " 'ncit:c3563': 'http://purl.obolibrary.org/obo/MONDO_0002464',\n",
-       " 'gard:0006289': 'http://purl.obolibrary.org/obo/MONDO_0009380',\n",
-       " 'icd10:d80.5': 'http://purl.obolibrary.org/obo/MONDO_0011528',\n",
-       " 'sctid:763530000': 'http://purl.obolibrary.org/obo/MONDO_0015605',\n",
-       " 'cohd:317895': 'http://purl.obolibrary.org/obo/MONDO_0006947',\n",
-       " 'gard:0002878': 'http://purl.obolibrary.org/obo/MONDO_0010926',\n",
-       " 'umls:c1332882': 'http://purl.obolibrary.org/obo/MONDO_0003641',\n",
-       " 'orphanet:227786': 'http://purl.obolibrary.org/obo/MONDO_0016420',\n",
-       " 'orphanet:294023': 'http://purl.obolibrary.org/obo/MONDO_0017411',\n",
-       " 'mesh:c538262': 'http://purl.obolibrary.org/obo/MONDO_0009719',\n",
-       " 'mesh:c562688': 'http://purl.obolibrary.org/obo/MONDO_0009924',\n",
-       " 'sctid:720830009': 'http://purl.obolibrary.org/obo/MONDO_0008956',\n",
-       " 'ncit:c96479': 'http://purl.obolibrary.org/obo/MONDO_0006498',\n",
-       " 'orphanet:71212': 'http://purl.obolibrary.org/obo/MONDO_0009278',\n",
-       " 'umls:c0271441': 'http://purl.obolibrary.org/obo/MONDO_0021204',\n",
-       " 'gard:0005692': 'http://purl.obolibrary.org/obo/MONDO_0010720',\n",
-       " 'meddra:10013799': 'http://purl.obolibrary.org/obo/MONDO_0007473',\n",
-       " 'omim:612095': 'http://purl.obolibrary.org/obo/MONDO_0012796',\n",
-       " 'umls:c1855303': 'http://purl.obolibrary.org/obo/MONDO_0009581',\n",
-       " 'gard:0013103': 'http://purl.obolibrary.org/obo/MONDO_0019471',\n",
-       " 'umls:c0334417': 'http://purl.obolibrary.org/obo/MONDO_0021389',\n",
-       " 'ncit:c4108': 'http://purl.obolibrary.org/obo/MONDO_0002954',\n",
-       " 'omim:618648': 'http://purl.obolibrary.org/obo/MONDO_0032848',\n",
-       " 'mesh:c536347': 'http://purl.obolibrary.org/obo/MONDO_0007447',\n",
-       " 'efo:0003847': 'http://purl.obolibrary.org/obo/MONDO_0001071',\n",
-       " 'gard:0012724': 'http://purl.obolibrary.org/obo/MONDO_0020341',\n",
-       " 'icd9:281.2': 'http://purl.obolibrary.org/obo/MONDO_0001860',\n",
-       " 'sctid:722454003': 'http://purl.obolibrary.org/obo/MONDO_0009581',\n",
-       " 'umls:c0268350': 'http://purl.obolibrary.org/obo/MONDO_0019571',\n",
-       " 'efo:0007126': 'http://purl.obolibrary.org/obo/MONDO_0005629',\n",
-       " 'umls:cn229293': 'http://purl.obolibrary.org/obo/MONDO_0014753',\n",
-       " 'umls:c1517113': 'http://purl.obolibrary.org/obo/MONDO_0021576',\n",
-       " 'mesh:c563509': 'http://purl.obolibrary.org/obo/MONDO_0008113',\n",
-       " 'umls:c1842039': 'http://purl.obolibrary.org/obo/MONDO_0007657',\n",
-       " 'omim:121201': 'http://purl.obolibrary.org/obo/MONDO_0007366',\n",
-       " 'umls:c2673427': 'http://purl.obolibrary.org/obo/MONDO_0009605',\n",
-       " 'icd10:p00.4': 'http://purl.obolibrary.org/obo/MONDO_0016018',\n",
-       " 'omim:616531': 'http://purl.obolibrary.org/obo/MONDO_0014679',\n",
-       " 'mesh:c564264': 'http://purl.obolibrary.org/obo/MONDO_0012005',\n",
-       " 'mesh:d007027': 'http://purl.obolibrary.org/obo/MONDO_0002150',\n",
-       " 'umls:c4304641': 'http://purl.obolibrary.org/obo/MONDO_0013182',\n",
-       " 'umls:c0475801': 'http://purl.obolibrary.org/obo/MONDO_0019461',\n",
-       " 'doid:332': 'http://purl.obolibrary.org/obo/MONDO_0004976',\n",
-       " 'umls:c4274329': 'http://purl.obolibrary.org/obo/MONDO_0015084',\n",
-       " 'ncit:c7064': 'http://purl.obolibrary.org/obo/MONDO_0020703',\n",
-       " 'orphanet:97242': 'http://purl.obolibrary.org/obo/MONDO_0019950',\n",
-       " 'omim:301029': 'http://purl.obolibrary.org/obo/MONDO_0026727',\n",
-       " 'sctid:719045009': 'http://purl.obolibrary.org/obo/MONDO_0018885',\n",
-       " 'orphanet:2328': 'http://purl.obolibrary.org/obo/MONDO_0009483',\n",
-       " 'icd10:l70.2': 'http://purl.obolibrary.org/obo/MONDO_0011438',\n",
-       " 'omim:614080': 'http://purl.obolibrary.org/obo/MONDO_0013563',\n",
-       " 'umls:c0206701': 'http://purl.obolibrary.org/obo/MONDO_0005278',\n",
-       " 'doid:11190': 'http://purl.obolibrary.org/obo/MONDO_0001217',\n",
-       " 'doid:11353': 'http://purl.obolibrary.org/obo/MONDO_0007197',\n",
-       " 'omim:617062': 'http://purl.obolibrary.org/obo/MONDO_0014893',\n",
-       " 'icd10:t51.1': 'http://purl.obolibrary.org/obo/MONDO_0017860',\n",
-       " 'gard:0001616': 'http://purl.obolibrary.org/obo/MONDO_0008272',\n",
-       " 'icd10:i45.81': 'http://purl.obolibrary.org/obo/MONDO_0002442',\n",
-       " 'omim:105565': 'http://purl.obolibrary.org/obo/MONDO_0007107',\n",
-       " 'sctid:51387008': 'http://purl.obolibrary.org/obo/MONDO_0006022',\n",
-       " 'orphanet:353277': 'http://purl.obolibrary.org/obo/MONDO_0008393',\n",
-       " 'icd10:g11.4': 'http://purl.obolibrary.org/obo/MONDO_0011489',\n",
-       " 'umls:c0155068': 'http://purl.obolibrary.org/obo/MONDO_0001037',\n",
-       " 'umls:c3553785': 'http://purl.obolibrary.org/obo/MONDO_0013899',\n",
-       " 'icd10:a02.0': 'http://purl.obolibrary.org/obo/MONDO_0017944',\n",
-       " 'cohd:378414': 'http://purl.obolibrary.org/obo/MONDO_0008375',\n",
-       " 'orphanet:293864': 'http://purl.obolibrary.org/obo/MONDO_0017400',\n",
-       " 'icd10:a81.0': 'http://purl.obolibrary.org/obo/MONDO_0005357',\n",
-       " 'mesh:d020254': 'http://purl.obolibrary.org/obo/MONDO_0008007',\n",
-       " 'sctid:58275005': 'http://purl.obolibrary.org/obo/MONDO_0008297',\n",
-       " 'sctid:7678002': 'http://purl.obolibrary.org/obo/MONDO_0024354',\n",
-       " 'umls:cn226154': 'http://purl.obolibrary.org/obo/MONDO_0018438',\n",
-       " 'orphanet:3305': 'http://purl.obolibrary.org/obo/MONDO_0018026',\n",
-       " 'efo:0003769': 'http://purl.obolibrary.org/obo/MONDO_0002082',\n",
-       " 'icd10:e74.2': 'http://purl.obolibrary.org/obo/MONDO_0017691',\n",
-       " 'mesh:d019687': 'http://purl.obolibrary.org/obo/MONDO_0004936',\n",
-       " 'oncotree:spc': 'http://purl.obolibrary.org/obo/MONDO_0044783',\n",
-       " 'umls:c1859598': 'http://purl.obolibrary.org/obo/MONDO_0008842',\n",
-       " 'gard:0010198': 'http://purl.obolibrary.org/obo/MONDO_0012291',\n",
-       " 'omim:615989': 'http://purl.obolibrary.org/obo/MONDO_0014440',\n",
-       " 'umls:c1845294': 'http://purl.obolibrary.org/obo/MONDO_0010349',\n",
-       " 'umls:c1865639': 'http://purl.obolibrary.org/obo/MONDO_0011215',\n",
-       " 'umls:c2676465': 'http://purl.obolibrary.org/obo/MONDO_0012891',\n",
-       " 'doid:4394': 'http://purl.obolibrary.org/obo/MONDO_0002984',\n",
-       " 'efo:1001782': 'http://purl.obolibrary.org/obo/MONDO_0020812',\n",
-       " 'oncotree:nbl': 'http://purl.obolibrary.org/obo/MONDO_0005072',\n",
-       " 'gard:0008252': 'http://purl.obolibrary.org/obo/MONDO_0006640',\n",
-       " 'orphanet:220493': 'http://purl.obolibrary.org/obo/MONDO_0016364',\n",
-       " 'doid:7050': 'http://purl.obolibrary.org/obo/MONDO_0004092',\n",
-       " 'hgnc:6535': 'http://purl.obolibrary.org/obo/MONDO_0013047',\n",
-       " 'orphanet:295067': 'http://purl.obolibrary.org/obo/MONDO_0017485',\n",
-       " 'umls:c2750785': 'http://purl.obolibrary.org/obo/MONDO_0013178',\n",
-       " 'icd9:259.51': 'http://purl.obolibrary.org/obo/MONDO_0019154',\n",
-       " 'sctid:400201008': 'http://purl.obolibrary.org/obo/MONDO_0024348',\n",
-       " 'icd9:748.1': 'http://purl.obolibrary.org/obo/MONDO_0015237',\n",
-       " 'icd10:q24.3': 'http://purl.obolibrary.org/obo/MONDO_0017866',\n",
-       " 'sctid:448254007': 'http://purl.obolibrary.org/obo/MONDO_0044887',\n",
-       " 'icd10:f01.1': 'http://purl.obolibrary.org/obo/MONDO_0014768',\n",
-       " 'icd9:306.51': 'http://purl.obolibrary.org/obo/MONDO_0000946',\n",
-       " 'doid:698': 'http://purl.obolibrary.org/obo/MONDO_0004059',\n",
-       " 'efo:0003016': 'http://purl.obolibrary.org/obo/MONDO_0005220',\n",
-       " 'umls:c1842381': 'http://purl.obolibrary.org/obo/MONDO_0011991',\n",
-       " 'icd9:147.9': 'http://purl.obolibrary.org/obo/MONDO_0021315',\n",
-       " 'orphanet:101987': 'http://purl.obolibrary.org/obo/MONDO_0015134',\n",
-       " 'ncit:c6461': 'http://purl.obolibrary.org/obo/MONDO_0003047',\n",
-       " 'icd10:a01.1': 'http://purl.obolibrary.org/obo/MONDO_0018626',\n",
-       " 'orphanet:85443': 'http://purl.obolibrary.org/obo/MONDO_0019438',\n",
-       " 'ncit:c131684': 'http://purl.obolibrary.org/obo/MONDO_0020696',\n",
-       " 'sctid:721221000': 'http://purl.obolibrary.org/obo/MONDO_0009342',\n",
-       " 'umls:c0154782': 'http://purl.obolibrary.org/obo/MONDO_0001503',\n",
-       " 'sctid:718124006': 'http://purl.obolibrary.org/obo/MONDO_0015487',\n",
-       " 'mesh:c563558': 'http://purl.obolibrary.org/obo/MONDO_0008028',\n",
-       " 'umls:c1858114': 'http://purl.obolibrary.org/obo/MONDO_0011487',\n",
-       " 'meddra:10002947': 'http://purl.obolibrary.org/obo/MONDO_0012456',\n",
-       " 'doid:26': 'http://purl.obolibrary.org/obo/MONDO_0002356',\n",
-       " 'icdo:8480/0': 'http://purl.obolibrary.org/obo/MONDO_0006859',\n",
-       " 'icd10:o43.90': 'http://purl.obolibrary.org/obo/MONDO_0005917',\n",
-       " 'umls:c0338428': 'http://purl.obolibrary.org/obo/MONDO_0000291',\n",
-       " 'doid:2658': 'http://purl.obolibrary.org/obo/MONDO_0002378',\n",
-       " 'icd10:d69.1': 'http://purl.obolibrary.org/obo/MONDO_0016361',\n",
-       " 'sctid:86188000': 'http://purl.obolibrary.org/obo/MONDO_0006825',\n",
-       " 'umls:c0393520': 'http://purl.obolibrary.org/obo/MONDO_0008938',\n",
-       " 'mesh:c567855': 'http://purl.obolibrary.org/obo/MONDO_0013051',\n",
-       " 'doid:4280': 'http://purl.obolibrary.org/obo/MONDO_0002937',\n",
-       " 'umls:c1861975': 'http://purl.obolibrary.org/obo/MONDO_0007245',\n",
-       " 'umls:cn203673': 'http://purl.obolibrary.org/obo/MONDO_0008389',\n",
-       " 'omim:617571': 'http://purl.obolibrary.org/obo/MONDO_0033091',\n",
-       " 'omim:226850': 'http://purl.obolibrary.org/obo/MONDO_0009188',\n",
-       " 'doid:9739': 'http://purl.obolibrary.org/obo/MONDO_0004866',\n",
-       " 'cohd:435789': 'http://purl.obolibrary.org/obo/MONDO_0001700',\n",
-       " 'umls:c1859785': 'http://purl.obolibrary.org/obo/MONDO_0008790',\n",
-       " 'icd10:n04.0': 'http://purl.obolibrary.org/obo/MONDO_0019653',\n",
-       " 'mesh:c565647': 'http://purl.obolibrary.org/obo/MONDO_0009080',\n",
-       " 'omim:269200': 'http://purl.obolibrary.org/obo/MONDO_0010012',\n",
-       " 'mesh:c563394': 'http://purl.obolibrary.org/obo/MONDO_0010988',\n",
-       " 'icd10:q87.1': 'http://purl.obolibrary.org/obo/MONDO_0010035',\n",
-       " 'sctid:422437002': 'http://purl.obolibrary.org/obo/MONDO_0010655',\n",
-       " 'icd10:q05.6': 'http://purl.obolibrary.org/obo/MONDO_0017062',\n",
-       " 'gard:0005856': 'http://purl.obolibrary.org/obo/MONDO_0005657',\n",
-       " 'sctid:10170007': 'http://purl.obolibrary.org/obo/MONDO_0009196',\n",
-       " 'icd9:200.6': 'http://purl.obolibrary.org/obo/MONDO_0020325',\n",
-       " 'sctid:254896002': 'http://purl.obolibrary.org/obo/MONDO_0002205',\n",
-       " 'icd10:c22.0': 'http://purl.obolibrary.org/obo/MONDO_0007256',\n",
-       " 'mesh:c566068': 'http://purl.obolibrary.org/obo/MONDO_0008540',\n",
-       " 'umls:c1141890': 'http://purl.obolibrary.org/obo/MONDO_0019171',\n",
-       " 'ncit:c119058': 'http://purl.obolibrary.org/obo/MONDO_0012316',\n",
-       " 'omim:616155': 'http://purl.obolibrary.org/obo/MONDO_0014511',\n",
-       " 'sctid:235595009': 'http://purl.obolibrary.org/obo/MONDO_0007186',\n",
-       " 'umls:c0205874': 'http://purl.obolibrary.org/obo/MONDO_0001825',\n",
-       " 'umls:c1855347': 'http://purl.obolibrary.org/obo/MONDO_0009566',\n",
-       " 'sctid:128188000': 'http://purl.obolibrary.org/obo/MONDO_0006497',\n",
-       " 'doid:6809': 'http://purl.obolibrary.org/obo/MONDO_0003998',\n",
-       " 'mesh:c566069': 'http://purl.obolibrary.org/obo/MONDO_0016675',\n",
-       " 'omim:613729': 'http://purl.obolibrary.org/obo/MONDO_0013393',\n",
-       " 'ncit:c28432': 'http://purl.obolibrary.org/obo/MONDO_0003950',\n",
-       " 'gard:0004801': 'http://purl.obolibrary.org/obo/MONDO_0010956',\n",
-       " 'icd9:327.42': 'http://purl.obolibrary.org/obo/MONDO_0005937',\n",
-       " 'ncit:c2922': 'http://purl.obolibrary.org/obo/MONDO_0003264',\n",
-       " 'meddra:10068335': 'http://purl.obolibrary.org/obo/MONDO_0008832',\n",
-       " 'orphanet:3459': 'http://purl.obolibrary.org/obo/MONDO_0010665',\n",
-       " 'icd10:e24.0': 'http://purl.obolibrary.org/obo/MONDO_0009050',\n",
-       " 'umls:c4225193': 'http://purl.obolibrary.org/obo/MONDO_0014787',\n",
-       " 'mesh:d008172': 'http://purl.obolibrary.org/obo/MONDO_0005766',\n",
-       " 'umls:c1837527': 'http://purl.obolibrary.org/obo/MONDO_0012102',\n",
-       " 'umls:cn199975': 'http://purl.obolibrary.org/obo/MONDO_0015602',\n",
-       " 'umls:c1096283': 'http://purl.obolibrary.org/obo/MONDO_0024410',\n",
-       " 'doid:11759': 'http://purl.obolibrary.org/obo/MONDO_0001357',\n",
-       " 'ncit:c124057': 'http://purl.obolibrary.org/obo/MONDO_0016759',\n",
-       " 'sctid:70189005': 'http://purl.obolibrary.org/obo/MONDO_0023164',\n",
-       " 'orphanet:220386': 'http://purl.obolibrary.org/obo/MONDO_0016355',\n",
-       " 'wikipedia:pemphigus': 'http://purl.obolibrary.org/obo/MONDO_0006594',\n",
-       " 'orphanet:254807': 'http://purl.obolibrary.org/obo/MONDO_0016797',\n",
-       " 'umls:c1970239': 'http://purl.obolibrary.org/obo/MONDO_0012602',\n",
-       " 'mesh:d018195': 'http://purl.obolibrary.org/obo/MONDO_0005636',\n",
-       " 'sctid:41910004': 'http://purl.obolibrary.org/obo/MONDO_0004934',\n",
-       " 'omim:616409': 'http://purl.obolibrary.org/obo/MONDO_0014625',\n",
-       " 'cohd:376112': 'http://purl.obolibrary.org/obo/MONDO_0001583',\n",
-       " 'ncit:c9300': 'http://purl.obolibrary.org/obo/MONDO_0010643',\n",
-       " 'efo:1001226': 'http://purl.obolibrary.org/obo/MONDO_0007008',\n",
-       " 'sctid:399343007': 'http://purl.obolibrary.org/obo/MONDO_0021072',\n",
-       " 'mesh:d010217': 'http://purl.obolibrary.org/obo/MONDO_0005913',\n",
-       " 'efo:0004288': 'http://purl.obolibrary.org/obo/MONDO_0005401',\n",
-       " 'orphanet:464359': 'http://purl.obolibrary.org/obo/MONDO_0018738',\n",
-       " 'mesh:d003095': 'http://purl.obolibrary.org/obo/MONDO_0021103',\n",
-       " 'umls:cn206699': 'http://purl.obolibrary.org/obo/MONDO_0019764',\n",
-       " 'omim:159600': 'http://purl.obolibrary.org/obo/MONDO_0008041',\n",
-       " 'orphanet:262182': 'http://purl.obolibrary.org/obo/MONDO_0016920',\n",
-       " 'doid:0080158': 'http://purl.obolibrary.org/obo/MONDO_0015288',\n",
-       " 'gard:0002285': 'http://purl.obolibrary.org/obo/MONDO_0009222',\n",
-       " 'efo:0008610': 'http://purl.obolibrary.org/obo/MONDO_0008109',\n",
-       " 'gard:0009559': 'http://purl.obolibrary.org/obo/MONDO_0005971',\n",
-       " 'icd10:h81.09': 'http://purl.obolibrary.org/obo/MONDO_0007972',\n",
-       " 'icd9:277.30': 'http://purl.obolibrary.org/obo/MONDO_0019065',\n",
-       " 'omim:150590': 'http://purl.obolibrary.org/obo/MONDO_0007884',\n",
-       " 'icd9:405.9': 'http://purl.obolibrary.org/obo/MONDO_0001200',\n",
-       " 'gard:0009688': 'http://purl.obolibrary.org/obo/MONDO_0007379',\n",
-       " 'omim:256550': 'http://purl.obolibrary.org/obo/MONDO_0009738',\n",
-       " 'cohd:252235': 'http://purl.obolibrary.org/obo/MONDO_0004813',\n",
-       " 'ncit:c119056': 'http://purl.obolibrary.org/obo/MONDO_0013021',\n",
-       " 'orphanet:1317': 'http://purl.obolibrary.org/obo/MONDO_0020742',\n",
-       " 'gard:0008520': 'http://purl.obolibrary.org/obo/MONDO_0010574',\n",
-       " 'doid:10325': 'http://purl.obolibrary.org/obo/MONDO_0005960',\n",
-       " 'gard:0012999': 'http://purl.obolibrary.org/obo/MONDO_0014773',\n",
-       " 'orphanet:47044': 'http://purl.obolibrary.org/obo/MONDO_0017884',\n",
-       " 'omim:614615': 'http://purl.obolibrary.org/obo/MONDO_0013824',\n",
-       " 'icd10:e74.8': 'http://purl.obolibrary.org/obo/MONDO_0017296',\n",
-       " 'umls:c2751683': 'http://purl.obolibrary.org/obo/MONDO_0013082',\n",
-       " 'meddra:10056667': 'http://purl.obolibrary.org/obo/MONDO_0015453',\n",
-       " 'doid:5983': 'http://purl.obolibrary.org/obo/MONDO_0003721',\n",
-       " 'doid:7907': 'http://purl.obolibrary.org/obo/MONDO_0004393',\n",
-       " 'meddra:10067866': 'http://purl.obolibrary.org/obo/MONDO_0016027',\n",
-       " 'gard:0007060': 'http://purl.obolibrary.org/obo/MONDO_0022180',\n",
-       " 'orphanet:3322': 'http://purl.obolibrary.org/obo/MONDO_0018045',\n",
-       " 'icd10:q87.2': 'http://purl.obolibrary.org/obo/MONDO_0018249',\n",
-       " 'omim:617022': 'http://purl.obolibrary.org/obo/MONDO_0014870',\n",
-       " 'gard:0010632': 'http://purl.obolibrary.org/obo/MONDO_0016693',\n",
-       " 'icd10:m48.2': 'http://purl.obolibrary.org/obo/MONDO_0003956',\n",
-       " 'sctid:718609003': 'http://purl.obolibrary.org/obo/MONDO_0011948',\n",
-       " 'mesh:d014063': 'http://purl.obolibrary.org/obo/MONDO_0007655',\n",
-       " 'omim:613690': 'http://purl.obolibrary.org/obo/MONDO_0013369',\n",
-       " 'orphanet:79406': 'http://purl.obolibrary.org/obo/MONDO_0019309',\n",
-       " ...}"
-      ]
-     },
-     "execution_count": 86,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "mondo_dbxref_dict"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 87,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# pickle dictionary\n",
-    "pickle.dump(mondo_dbxref_dict, open(processed_data_location + 'Mondo_DOID_Identifier_Map.pkl', 'wb'), protocol=4)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***\n",
-    "\n",
-    "### Human Protein Atlas/GTEx Tissue/Cells - UBERON + Cell Ontology + Cell Line Ontology <a class=\"anchor\" id=\"hpa-uberon\"></a>\n",
-    "\n",
-    "**Data Source Wiki Page:**  \n",
-    "- [human-protein-atlas](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources/#human-protein-atlas) \n",
-    "- [genotype-tissue-expression-project](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#the-genotype-tissue-expression-gtex-project)  \n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Purpose:** Downloads a query for cell, tissue, and blood types with overexpressed protein-coding genes in the human proteome ([`proteinatlas_search.tsv`](https://www.proteinatlas.org/api/search_download.php?search=&columns=g,eg,up,pe,rnatsm,rnaclsm,rnacasm,rnabrsm,rnabcsm,rnablsm,scl,t_RNA_adipose_tissue,t_RNA_adrenal_gland,t_RNA_amygdala,t_RNA_appendix,t_RNA_basal_ganglia,t_RNA_bone_marrow,t_RNA_breast,t_RNA_cerebellum,t_RNA_cerebral_cortex,t_RNA_cervix,_uterine,t_RNA_colon,t_RNA_corpus_callosum,t_RNA_ductus_deferens,t_RNA_duodenum,t_RNA_endometrium_1,t_RNA_epididymis,t_RNA_esophagus,t_RNA_fallopian_tube,t_RNA_gallbladder,t_RNA_heart_muscle,t_RNA_hippocampal_formation,t_RNA_hypothalamus,t_RNA_kidney,t_RNA_liver,t_RNA_lung,t_RNA_lymph_node,t_RNA_midbrain,t_RNA_olfactory_region,t_RNA_ovary,t_RNA_pancreas,t_RNA_parathyroid_gland,t_RNA_pituitary_gland,t_RNA_placenta,t_RNA_pons_and_medulla,t_RNA_prostate,t_RNA_rectum,t_RNA_retina,t_RNA_salivary_gland,t_RNA_seminal_vesicle,t_RNA_skeletal_muscle,t_RNA_skin_1,t_RNA_small_intestine,t_RNA_smooth_muscle,t_RNA_spinal_cord,t_RNA_spleen,t_RNA_stomach_1,t_RNA_testis,t_RNA_thalamus,t_RNA_thymus,t_RNA_thyroid_gland,t_RNA_tongue,t_RNA_tonsil,t_RNA_urinary_bladder,t_RNA_vagina,t_RNA_B-cells,t_RNA_dendritic_cells,t_RNA_granulocytes,t_RNA_monocytes,t_RNA_NK-cells,t_RNA_T-cells,t_RNA_total_PBMC,cell_RNA_A-431,cell_RNA_A549,cell_RNA_AF22,cell_RNA_AN3-CA,cell_RNA_ASC_diff,cell_RNA_ASC_TERT1,cell_RNA_BEWO,cell_RNA_BJ,cell_RNA_BJ_hTERT+,cell_RNA_BJ_hTERT+_SV40_Large_T+,cell_RNA_BJ_hTERT+_SV40_Large_T+_RasG12V,cell_RNA_CACO-2,cell_RNA_CAPAN-2,cell_RNA_Daudi,cell_RNA_EFO-21,cell_RNA_fHDF/TERT166,cell_RNA_HaCaT,cell_RNA_HAP1,cell_RNA_HBEC3-KT,cell_RNA_HBF_TERT88,cell_RNA_HDLM-2,cell_RNA_HEK_293,cell_RNA_HEL,cell_RNA_HeLa,cell_RNA_Hep_G2,cell_RNA_HHSteC,cell_RNA_HL-60,cell_RNA_HMC-1,cell_RNA_HSkMC,cell_RNA_hTCEpi,cell_RNA_hTEC/SVTERT24-B,cell_RNA_hTERT-HME1,cell_RNA_HUVEC_TERT2,cell_RNA_K-562,cell_RNA_Karpas-707,cell_RNA_LHCN-M2,cell_RNA_MCF7,cell_RNA_MOLT-4,cell_RNA_NB-4,cell_RNA_NTERA-2,cell_RNA_PC-3,cell_RNA_REH,cell_RNA_RH-30,cell_RNA_RPMI-8226,cell_RNA_RPTEC_TERT1,cell_RNA_RT4,cell_RNA_SCLC-21H,cell_RNA_SH-SY5Y,cell_RNA_SiHa,cell_RNA_SK-BR-3,cell_RNA_SK-MEL-30,cell_RNA_T-47d,cell_RNA_THP-1,cell_RNA_TIME,cell_RNA_U-138_MG,cell_RNA_U-2_OS,cell_RNA_U-2197,cell_RNA_U-251_MG,cell_RNA_U-266/70,cell_RNA_U-266/84,cell_RNA_U-698,cell_RNA_U-87_MG,cell_RNA_U-937,cell_RNA_WM-115,blood_RNA_basophil,blood_RNA_classical_monocyte,blood_RNA_eosinophil,blood_RNA_gdT-cell,blood_RNA_intermediate_monocyte,blood_RNA_MAIT_T-cell,blood_RNA_memory_B-cell,blood_RNA_memory_CD4_T-cell,blood_RNA_memory_CD8_T-cell,blood_RNA_myeloid_DC,blood_RNA_naive_B-cell,blood_RNA_naive_CD4_T-cell,blood_RNA_naive_CD8_T-cell,blood_RNA_neutrophil,blood_RNA_NK-cell,blood_RNA_non-classical_monocyte,blood_RNA_plasmacytoid_DC,blood_RNA_T-reg,blood_RNA_total_PBMC,brain_RNA_amygdala,brain_RNA_basal_ganglia,brain_RNA_cerebellum,brain_RNA_cerebral_cortex,brain_RNA_hippocampal_formation,brain_RNA_hypothalamus,brain_RNA_midbrain,brain_RNA_olfactory_region,brain_RNA_pons_and_medulla,brain_RNA_thalamus&format=tsv)) via [API](https://www.proteinatlas.org/about/help/dataaccess) and median gene-level TPM by tissue for all genes that are not protein-coding ([`GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct`](https://storage.googleapis.com/gtex_analysis_v8/rna_seq_data/GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct.gz)) in order to create mappings between cell and tissue type strings to the Uber-Anatomy, Cell Ontology, and Cell Line Ontology concepts (see [human-protein-atlas](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#human-protein-atlas) for details on the mapping process). The mappings are then used to create the following edge types:  \n",
-    "- rna-cell line  \n",
-    "- rna-tissue type   \n",
-    "- protein-cell line  \n",
-    "- protein-tissue type  \n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Output:**  \n",
-    "- All HPA tissue and cell type strings ➞ [`HPA_tissues.txt`](https://www.dropbox.com/s/0w6lbrit4mag92h/HPA_tissues.txt?dl=1)  \n",
-    "- Mapping HPA strings to ontology concepts (documentation) ➞ [`zooma_tissue_cell_mapping_04JAN2020.xlsx`](https://www.dropbox.com/s/0dh8tg8g4yvrl06/zooma_tissue_cell_mapping_04JAN2020.xlsx?dl=1)  \n",
-    "- Final HPA-ontology mappings ➞ [`HPA_GTEx_TISSUE_CELL_MAP.txt`](https://www.dropbox.com/s/at00xui224syzh8/HPA_GTEx_TISSUE_CELL_MAP.txt?dl=1)\n",
-    "- HPA Edges ➞ [`HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt`](https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Human Protein Atlas**"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Download Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "url = 'https://www.proteinatlas.org/api/search_download.php?search=&columns=g,eg,up,pe,rnatsm,rnaclsm,rnacasm,rnabrsm,rnabcsm,rnablsm,scl,t_RNA_adipose_tissue,t_RNA_adrenal_gland,t_RNA_amygdala,t_RNA_appendix,t_RNA_basal_ganglia,t_RNA_bone_marrow,t_RNA_breast,t_RNA_cerebellum,t_RNA_cerebral_cortex,t_RNA_cervix,_uterine,t_RNA_colon,t_RNA_corpus_callosum,t_RNA_ductus_deferens,t_RNA_duodenum,t_RNA_endometrium_1,t_RNA_epididymis,t_RNA_esophagus,t_RNA_fallopian_tube,t_RNA_gallbladder,t_RNA_heart_muscle,t_RNA_hippocampal_formation,t_RNA_hypothalamus,t_RNA_kidney,t_RNA_liver,t_RNA_lung,t_RNA_lymph_node,t_RNA_midbrain,t_RNA_olfactory_region,t_RNA_ovary,t_RNA_pancreas,t_RNA_parathyroid_gland,t_RNA_pituitary_gland,t_RNA_placenta,t_RNA_pons_and_medulla,t_RNA_prostate,t_RNA_rectum,t_RNA_retina,t_RNA_salivary_gland,t_RNA_seminal_vesicle,t_RNA_skeletal_muscle,t_RNA_skin_1,t_RNA_small_intestine,t_RNA_smooth_muscle,t_RNA_spinal_cord,t_RNA_spleen,t_RNA_stomach_1,t_RNA_testis,t_RNA_thalamus,t_RNA_thymus,t_RNA_thyroid_gland,t_RNA_tongue,t_RNA_tonsil,t_RNA_urinary_bladder,t_RNA_vagina,t_RNA_B-cells,t_RNA_dendritic_cells,t_RNA_granulocytes,t_RNA_monocytes,t_RNA_NK-cells,t_RNA_T-cells,t_RNA_total_PBMC,cell_RNA_A-431,cell_RNA_A549,cell_RNA_AF22,cell_RNA_AN3-CA,cell_RNA_ASC_diff,cell_RNA_ASC_TERT1,cell_RNA_BEWO,cell_RNA_BJ,cell_RNA_BJ_hTERT+,cell_RNA_BJ_hTERT+_SV40_Large_T+,cell_RNA_BJ_hTERT+_SV40_Large_T+_RasG12V,cell_RNA_CACO-2,cell_RNA_CAPAN-2,cell_RNA_Daudi,cell_RNA_EFO-21,cell_RNA_fHDF/TERT166,cell_RNA_HaCaT,cell_RNA_HAP1,cell_RNA_HBEC3-KT,cell_RNA_HBF_TERT88,cell_RNA_HDLM-2,cell_RNA_HEK_293,cell_RNA_HEL,cell_RNA_HeLa,cell_RNA_Hep_G2,cell_RNA_HHSteC,cell_RNA_HL-60,cell_RNA_HMC-1,cell_RNA_HSkMC,cell_RNA_hTCEpi,cell_RNA_hTEC/SVTERT24-B,cell_RNA_hTERT-HME1,cell_RNA_HUVEC_TERT2,cell_RNA_K-562,cell_RNA_Karpas-707,cell_RNA_LHCN-M2,cell_RNA_MCF7,cell_RNA_MOLT-4,cell_RNA_NB-4,cell_RNA_NTERA-2,cell_RNA_PC-3,cell_RNA_REH,cell_RNA_RH-30,cell_RNA_RPMI-8226,cell_RNA_RPTEC_TERT1,cell_RNA_RT4,cell_RNA_SCLC-21H,cell_RNA_SH-SY5Y,cell_RNA_SiHa,cell_RNA_SK-BR-3,cell_RNA_SK-MEL-30,cell_RNA_T-47d,cell_RNA_THP-1,cell_RNA_TIME,cell_RNA_U-138_MG,cell_RNA_U-2_OS,cell_RNA_U-2197,cell_RNA_U-251_MG,cell_RNA_U-266/70,cell_RNA_U-266/84,cell_RNA_U-698,cell_RNA_U-87_MG,cell_RNA_U-937,cell_RNA_WM-115,blood_RNA_basophil,blood_RNA_classical_monocyte,blood_RNA_eosinophil,blood_RNA_gdT-cell,blood_RNA_intermediate_monocyte,blood_RNA_MAIT_T-cell,blood_RNA_memory_B-cell,blood_RNA_memory_CD4_T-cell,blood_RNA_memory_CD8_T-cell,blood_RNA_myeloid_DC,blood_RNA_naive_B-cell,blood_RNA_naive_CD4_T-cell,blood_RNA_naive_CD8_T-cell,blood_RNA_neutrophil,blood_RNA_NK-cell,blood_RNA_non-classical_monocyte,blood_RNA_plasmacytoid_DC,blood_RNA_T-reg,blood_RNA_total_PBMC,brain_RNA_amygdala,brain_RNA_basal_ganglia,brain_RNA_cerebellum,brain_RNA_cerebral_cortex,brain_RNA_hippocampal_formation,brain_RNA_hypothalamus,brain_RNA_midbrain,brain_RNA_olfactory_region,brain_RNA_pons_and_medulla,brain_RNA_thalamus&format=tsv'\n",
-    "data_downloader(url, unprocessed_data_location, 'proteinatlas_search.tsv.gz')"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Load Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "hpa = pandas.read_csv(unprocessed_data_location + 'proteinatlas_search.tsv',\n",
-    "                      header=0,\n",
-    "                      delimiter='\\t')\n",
-    "\n",
-    "# replace NaN with 'None'\n",
-    "hpa.fillna('None', inplace=True)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Identify HPA Terms Needing Mapping_  \n",
-    "To expedite the mapping process, all HPA tissues, cells, cell lines, and fluid types are extracted from the HPA data columns."
+    "# load data\n",
+    "hpa = pandas.read_csv(unprocessed_data_location + 'proteinatlas_search.tsv', header=0, delimiter='\\t')\n",
+    "hpa.fillna('None', inplace=True)"
    ]
   },
   {
@@ -6523,31 +2507,20 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# retrieve terms to map\n",
-    "terms_to_map = list(hpa.columns)\n",
-    "\n",
-    "# write results\n",
+    "# retrieve terms to map and write results\n",
     "with open(unprocessed_data_location + 'HPA_tissues.txt', 'w') as outfile:\n",
-    "    for x in tqdm(terms_to_map):\n",
+    "    for x in tqdm(list(hpa.columns)):\n",
     "        if x.endswith('[NX]'):\n",
-    "            term = x.split('RNA - ')[-1].split(' [NX]')[:-1][0]\n",
-    "            outfile.write(term + '\\n')\n",
-    "\n",
-    "outfile.close()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Genotype-Tissue Expression Project**"
+    "            outfile.write(x.split('RNA - ')[-1].split(' [NX]')[:-1][0] + '\\n')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Download Data_"
+    "***\n",
+    "**Genotype-Tissue Expression Project**  \n",
+    "Import the tissues, cells, cell lines, and fluids that we externally mapped from HPA and GTEx data to [UBERON](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#uber-anatomy-ontology), the [Cell Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#cell-ontology), and the [Cell Line Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#cell-line-ontology)."
    ]
   },
   {
@@ -6556,41 +2529,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# load data\n",
     "url='https://storage.googleapis.com/gtex_analysis_v8/rna_seq_data/GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Load Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "gtex = pandas.read_csv(unprocessed_data_location + 'GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct',\n",
-    "                       header=0,\n",
-    "                       skiprows=2,\n",
-    "                       delimiter='\\t')\n",
-    "\n",
-    "# replace NaN with 'None'\n",
-    "gtex.fillna('None', inplace=True)\n",
+    "if not os.path.exists(unprocessed_data_location + 'GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
     "\n",
-    "# remove identifier type, which appears after '.'\n",
-    "gtex['Name'].replace('(\\..*)','', inplace=True, regex=True)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Get Mapping Data**   \n",
-    "Import the tissues, cells, cell lines, and fluids that we externally mapped from HPA and GTEx data to [UBERON](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#uber-anatomy-ontology), the [Cell Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#cell-ontology), and the [Cell Line Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#cell-line-ontology)."
+    "# load data\n",
+    "gtex = pandas.read_csv(unprocessed_data_location + 'GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct', header=0, skiprows=2, delimiter='\\t')\n",
+    "gtex.fillna('None', inplace=True)  # replace NaN with 'None'\n",
+    "gtex['Name'].replace('(\\..*)','', inplace=True, regex=True)  # remove identifier type, which appears after '.'\n"
    ]
   },
   {
@@ -6599,12 +2546,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
+    "url='https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/zooma_tissue_cell_mapping_04JAN2020.xlsx'\n",
+    "if not os.path.exists(unprocessed_data_location + 'zooma_tissue_cell_mapping_04JAN2020.xlsx'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "    \n",
+    "# load ontology mapping data\n",
     "mapping_data = pandas.read_excel(open(unprocessed_data_location + 'zooma_tissue_cell_mapping_04JAN2020.xlsx', 'rb'),\n",
-    "                                 sheet_name='Concept_Mapping - 04JAN2020',\n",
-    "                                 header=0)\n",
-    "\n",
-    "# convert NaN to None\n",
-    "mapping_data.fillna('None', inplace=True)\n",
+    "                                 sheet_name='Concept_Mapping - 04JAN2020', header=0, engine='openpyxl')\n",
+    "mapping_data.fillna('None', inplace=True)  # convert NaN to None\n",
     "\n",
     "# preview data\n",
     "mapping_data.head(n=3)"
@@ -6624,35 +2574,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "with open(processed_data_location + 'HPA_GTEx_TISSUE_CELL_MAP.txt', 'w') as outfile:\n",
+    "with open(processed_data_location + 'HPA_GTEx_TISSUE_CELL_MAP.txt', 'w') as out:\n",
     "    for idx, row in tqdm(mapping_data.iterrows(), total=mapping_data.shape[0]):\n",
-    "        if row['UBERON ID'] != 'None':\n",
-    "            outfile.write(str(row['ORIGINAL TERM']).strip() + '\\t' + str(row['UBERON ID']).strip() + '\\n')\n",
-    "        if row['CL ID'] != 'None':\n",
-    "            outfile.write(str(row['ORIGINAL TERM']).strip() + '\\t' + str(row['CL ID']).strip() + '\\n')\n",
-    "        if row['CLO ID'] != 'None':\n",
-    "            outfile.write(str(row['ORIGINAL TERM']).strip() + '\\t' + str(row['CLO ID']).strip() + '\\n')\n",
-    "\n",
-    "outfile.close()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "mapping_data = pandas.read_csv(processed_data_location + 'HPA_GTEx_TISSUE_CELL_MAP.txt',\n",
-    "                               header=None,\n",
-    "                               names=['TISSUE_CELL_TERM', 'ONTOLOGY_IDs'],\n",
-    "                               delimiter='\\t')"
+    "        if row['UBERON'] != 'None': out.write(str(row['TERM']).strip() + '\\t' + str(row['UBERON']).strip() + '\\n')\n",
+    "        if row['CL'] != 'None': out.write(str(row['TERM']).strip() + '\\t' + str(row['CL']).strip() + '\\n')\n",
+    "        if row['CLO'] != 'None': out.write(str(row['TERM']).strip() + '\\t' + str(row['CLO']).strip() + '\\n')"
    ]
   },
   {
@@ -6661,6 +2587,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# load mapping data\n",
+    "mapping_data = pandas.read_csv(processed_data_location + 'HPA_GTEx_TISSUE_CELL_MAP.txt', header=None, names=['TISSUE_CELL_TERM', 'ONTOLOGY_IDs'], delimiter='\\t')\n",
+    "\n",
+    "# preview data\n",
     "mapping_data.head(n=3)"
    ]
   },
@@ -6668,12 +2598,12 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "<br>\n",
+    "***\n",
     "\n",
     "**Create Edge Data Set**\n",
     "\n",
     "_Human Protein Atlas_  \n",
-    "The `HPA` data looped over and reformatted such all all tissue, cell, cell lines, and fluid types are stored as a nested list. As shown in the code chunk, you will see that the anatomy type is specified as an item in the list according to its type. This is done in order to make mapping more efficient while building the knowledge graph edge list."
+    "The `HPA` data is looped over and reformatted such that all tissue, cell, cell lines, and fluid types are stored as a nested list. The anatomy type is specified as an item in the list according to its type in order to make mapping more efficient while building the knowledge graph edge list."
    ]
   },
   {
@@ -6683,27 +2613,23 @@
    "outputs": [],
    "source": [
     "hpa_results = []\n",
-    "\n",
     "for idx, row in tqdm(hpa.iterrows(), total=hpa.shape[0]):\n",
+    "    ens, gene, uniprot, evid = str(row['Ensembl']), str(row['Gene']), str(row['Uniprot']), str(row['Evidence'])\n",
     "    if row['RNA tissue specific NX'] != 'None':\n",
     "        for x in row['RNA tissue specific NX'].split(';'):\n",
-    "            hpa_results += [[row['Ensembl'], row['Gene'], row['Uniprot'], row['Evidence'], 'anatomy', x.split(':')[0]]]\n",
-    "\n",
+    "            hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]\n",
     "    if row['RNA cell line specific NX'] != 'None':\n",
     "        for x in row['RNA cell line specific NX'].split(';'):\n",
-    "            hpa_results += [[row['Ensembl'], row['Gene'], row['Uniprot'], row['Evidence'], 'cell line', x.split(':')[0]]]\n",
-    "\n",
+    "            hpa_results += [[ens, gene, uniprot, evid, 'cell line', str(x.split(':')[0])]]\n",
     "    if row['RNA brain regional specific NX'] != 'None':\n",
     "        for x in row['RNA brain regional specific NX'].split(';'):\n",
-    "            hpa_results += [[row['Ensembl'], row['Gene'], row['Uniprot'], row['Evidence'], 'anatomy', x.split(':')[0]]]\n",
-    "\n",
+    "            hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]\n",
     "    if row['RNA blood cell specific NX'] != 'None':\n",
     "        for x in row['RNA blood cell specific NX'].split(';'):\n",
-    "            hpa_results += [[row['Ensembl'], row['Gene'], row['Uniprot'], row['Evidence'], 'anatomy', x.split(':')[0]]]\n",
-    "\n",
+    "            hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]\n",
     "    if row['RNA blood lineage specific NX'] != 'None':\n",
     "        for x in row['RNA blood lineage specific NX'].split(';'):\n",
-    "            hpa_results += [[row['Ensembl'], row['Gene'], row['Uniprot'], row['Evidence'], 'anatomy', x.split(':')[0]]]"
+    "            hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]"
    ]
   },
   {
@@ -6711,7 +2637,7 @@
    "metadata": {},
    "source": [
     "_Genotype-Tissue Expression Project_  \n",
-    "The `GTEx` edge data is created by first filtering out all _protein-coding_ genes that appear in the `HPA` cell transcriptome data set. Once filter so that we are only left noncoding genes, we perform an additional filtering step to only add genes and their corresponding tissue, cell, or fluid, if the median expression is `>= 1.0`. The `GTEx` is formatted such all all tissue, cell, and fluid types occur as their own column and all unique genes occur as a row, thus the expression filtering step is performed while also reformatting the file. The genes and tissues/cells/fluids that meet criteria are stored as a nested list."
+    "The `GTEx` edge data is created by first filtering out all _protein-coding_ genes that appear in the `HPA` cell transcriptome data set. Once filter so that we are only left noncoding genes, we perform an additional filtering step to only add genes and their corresponding tissue, cell, or fluid, if the median expression is `>= 1.0`. The `GTEx` is formatted such that all anatomical entities occur as their own column and all unique genes occur as a row, thus the expression filtering step is performed while also reformatting the file. The genes and tissues/cells/fluids that meet criteria are stored as a nested list."
    ]
   },
   {
@@ -6720,26 +2646,25 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# get a list of hpa protein-coding genes\n",
+    "# remove rows that contain protein coding genes already in the hpa data\n",
     "hpa_genes = list(hpa['Ensembl'].drop_duplicates(keep='first', inplace=False))\n",
-    "\n",
-    "# remove rows that contain protein coding genes\n",
     "gtex = gtex.loc[gtex['Name'].apply(lambda x: x not in hpa_genes)]\n",
     "\n",
     "# loop over data and re-organize - only keep results with tpm >= 1 and if gene symbol is not a protein-coding gene\n",
     "gtex_results = []\n",
-    "\n",
-    "for idx, row in tqdm(gtex.iterrows(), total=gtex.shape[0]):    \n",
+    "for idx, row in tqdm(gtex.iterrows(), total=gtex.shape[0]):\n",
     "    for col in list(gtex.columns)[2:]:\n",
-    "        if row[col] >= 1.0:           \n",
-    "            gtex_results += [[row['Name'], row['Description'], 'None', 'Evidence at transcript level', 'cell line' if 'Cells' in col else 'anatomy', col]]"
+    "        typ = 'cell line' if 'Cells' in col else 'anatomy'\n",
+    "        if row[col] >= 1.0:\n",
+    "            evidence = 'Evidence at transcript level'\n",
+    "            gtex_results += [[str(row['Name']), str(row['Description']), 'None', evidence, typ, str(col)]]"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Write Results_"
+    "*Writes Edge Data*  "
    ]
   },
   {
@@ -6748,18 +2673,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "with open(processed_data_location + 'HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt', 'w') as outfile:\n",
-    "    for res in tqdm(hpa_results + gtex_results):\n",
-    "        outfile.write(str(res[0]) + '\\t' + str(res[1]) + '\\t' + str(res[2]) + '\\t' + str(res[3]) + '\\t' + str(res[4]) + '\\t' + str(res[5]) + '\\n')\n",
-    "\n",
-    "outfile.close()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
+    "with open(processed_data_location + 'HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt', 'w') as out:\n",
+    "    for x in tqdm(hpa_results + gtex_results):\n",
+    "        out.write(x[0] + '\\t' + x[1] + '\\t' + x[2] + '\\t' + x[3] + '\\t' + x[4] + '\\t' + x[5] + '\\n')"
    ]
   },
   {
@@ -6768,21 +2684,12 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# load data, return edge count, and preview it\n",
     "hpa_edges = pandas.read_csv(processed_data_location + 'HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt',\n",
-    "                           header=None,\n",
-    "                           names=['Ensembl_IDs', 'Gene_Symbols', 'Uniport_IDs', 'Evidence', 'Anatomy_Type', 'Anatomy'],\n",
-    "                           low_memory=False,\n",
-    "                           sep='\\t')\n",
+    "                           header=None, low_memory=False, sep='\\t',\n",
+    "                           names=['Ensembl_IDs', 'Gene_Symbols', 'Uniport_IDs', 'Evidence', 'Anatomy_Type', 'Anatomy'])\n",
     "\n",
-    "print('There are {edge_count} edges'.format(edge_count=len(hpa_edges)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "print('There are {edge_count} edges'.format(edge_count=len(hpa_edges)))\n",
     "hpa_edges.head(n=5)"
    ]
   },
@@ -6807,14 +2714,16 @@
     "- `Reactome Pathway Identifiers`  ➞ `KEGG Pathway Identifiers` ➞ `Pathway Ontology Identifiers` \n",
     "\n",
     "**Output:**  \n",
-    "- [`REACTOME_PW_GO_MAPPINGS.txt`](https://www.dropbox.com/s/6752jvjt5p3qf2e/REACTOME_PW_GO_MAPPINGS.txt?dl=1)\n"
+    "- `REACTOME_PW_GO_MAPPINGS.txt`\n"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Download the Pathway Ontology**   \n",
+    "***\n",
+    "\n",
+    "**Pathway Ontology**   \n",
     "Use [OWL Tools](https://github.com/owlcollab/owltools/wiki) to download the [Pathway Ontology](http://www.obofoundry.org/ontology/pw.html). Once downloaded, we read the ontology in as a `RDFLib` graph object so that we can query it to obtain all `DbXRefs`."
    ]
   },
@@ -6824,48 +2733,17 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# download ontology using subprocess and OWLTOOLS in order to get the ontology and its imported ontologies\n",
-    "subprocess.check_call(['./pkt_kg/libs/owltools',\n",
-    "                       'http://purl.obolibrary.org/obo/pw.owl',\n",
-    "                       '--merge-import-closure',\n",
-    "                       '-o',\n",
-    "                       unprocessed_data_location + 'pw_with_imports.owl'])"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# load the knowledge graph\n",
-    "pw_graph = Graph()\n",
-    "pw_graph.parse(unprocessed_data_location + 'pw_with_imports.owl')\n",
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'pw_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/pw.owl',\n",
+    "                             unprocessed_data_location + 'pw_with_imports.owl'))\n",
     "\n",
+    "# load the knowledge graph\n",
+    "pw_graph = Graph().parse(unprocessed_data_location + 'pw_with_imports.owl')\n",
     "print('There are {} axioms in the ontology (date: {})'.format(len(pw_graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "results = pw_graph.query(\n",
-    "    \"\"\"SELECT DISTINCT ?c ?xref ?rel_syns ?exc_syns\n",
-    "           WHERE {\n",
-    "              ?c rdf:type owl:Class .\n",
-    "              ?c rdfs:label ?c_label .\n",
-    "              ?c_annot owl:annotatedSource ?c .\n",
-    "              ?c_annot oboInOwl:hasDbXref ?xref .\n",
-    "              ?c oboInOwl:hasRelatedSynonym ?rel_syns . \n",
-    "              ?c oboInOwl:hasExactSynonym ?exc_syns .}\n",
-    "           \"\"\", initNs={\"rdf\": 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',\n",
-    "                        \"rdfs\": 'http://www.w3.org/2000/01/rdf-schema#',\n",
-    "                        \"owl\": 'http://www.w3.org/2002/07/owl#',\n",
-    "                        \"oboInOwl\": 'http://www.geneontology.org/formats/oboInOwl#'}) "
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -6880,15 +2758,16 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "id_mappings = {}\n",
+    "# get dbxref results\n",
+    "dbxref_res = gets_ontology_class_dbxrefs(pw_graph)[0]\n",
+    "dbxref_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')} for k, v in dbxref_res.items() if 'PW_' in str(v)}\n",
+    "\n",
+    "# get synonym results\n",
+    "syn_res = gets_ontology_class_synonyms(pw_graph)[0]\n",
+    "synonym_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')} for k, v in syn_res.items() if 'PW_' in str(v)}\n",
     "\n",
-    "for res in tqdm(results):\n",
-    "    for x in res:\n",
-    "        if 'http' not in x and 'PMID' not in x:\n",
-    "            if str(x) in id_mappings.keys():\n",
-    "                id_mappings[str(x)] |= set([str(res[0])])\n",
-    "            else:\n",
-    "                id_mappings[str(x)] = set([str(res[0])])\n",
+    "# combine results into single dictionary\n",
+    "id_mappings = {**dbxref_dict, **synonym_dict}\n",
     "\n",
     "print('There are {} results (date: {})'.format(len(id_mappings), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
@@ -6897,7 +2776,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Download Human Reactome Pathways**  \n",
+    "***\n",
+    "\n",
+    "**Reactome Pathways**  \n",
     "Download a file of all [Reactome Pathways](https://reactome.org/download/current/ReactomePathways.txt), [Reactome's GO Annotations]('https://reactome.org/download/current/gene_association.reactome.gz'), and [Reactome's mappings to CHEBI](https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt). This file will be filtered to only include human pathways."
    ]
   },
@@ -6914,8 +2795,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'https://reactome.org/download/current/ReactomePathways.txt'\n",
-    "data_downloader(url, unprocessed_data_location)"
+    "if not os.path.exists(unprocessed_data_location + 'ReactomePathways.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "reactome_pathways = pandas.read_csv(unprocessed_data_location + 'ReactomePathways.txt', header=None, delimiter='\\t', low_memory=False)"
    ]
   },
   {
@@ -6924,17 +2810,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# read in reactome data\n",
-    "reactome_pathways = pandas.read_csv(unprocessed_data_location + 'ReactomePathways.txt',\n",
-    "                                    header=None,\n",
-    "                                    delimiter='\\t',\n",
-    "                                    low_memory=False)\n",
-    "\n",
-    "# remove all non-human pathways\n",
+    "# remove all non-human pathways and save as list\n",
     "reactome_pathways = reactome_pathways.loc[reactome_pathways[2].apply(lambda x: x == 'Homo sapiens')] \n",
-    "\n",
-    "# save as list\n",
-    "mapped_reactome_identifiers = {x:set(['PW_0000001']) for x in set(list(reactome_pathways[0]))}     \n"
+    "reactome_map = {x:set(['PW_0000001']) for x in set(list(reactome_pathways[0]))}     "
    ]
   },
   {
@@ -6950,8 +2828,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'https://reactome.org/download/current/gene_association.reactome.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
+    "if not os.path.exists(unprocessed_data_location + 'gene_association.reactome'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "reactome_pathways2 = pandas.read_csv(unprocessed_data_location + 'gene_association.reactome', header=None, delimiter='\\t', skiprows=1, low_memory=False)"
    ]
   },
   {
@@ -6960,18 +2843,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# read in reactome data\n",
-    "reactome_pathways2 = pandas.read_csv(unprocessed_data_location + 'gene_association.reactome',\n",
-    "                                     header=None,\n",
-    "                                     delimiter='\\t',\n",
-    "                                     skiprows=1,\n",
-    "                                     low_memory=False)\n",
-    "\n",
-    "# remove all non-human pathways\n",
+    "# remove all non-human pathways and save as list\n",
     "reactome_pathways2 = reactome_pathways2.loc[reactome_pathways2[12].apply(lambda x: x == 'taxon:9606')] \n",
-    "\n",
-    "# save as list\n",
-    "mapped_reactome_identifiers.update({x.split(':')[-1]:set(['PW_0000001']) for x in set(list(reactome_pathways2[5]))})     \n"
+    "reactome_map.update({x.split(':')[-1]:set(['PW_0000001']) for x in set(list(reactome_pathways2[5]))})     "
    ]
   },
   {
@@ -6987,8 +2861,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt'\n",
-    "data_downloader(url, unprocessed_data_location)"
+    "if not os.path.exists(unprocessed_data_location + 'ChEBI2Reactome_All_Levels.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "reactome_pathways3 = pandas.read_csv(unprocessed_data_location + 'ChEBI2Reactome_All_Levels.txt', header=None, delimiter='\\t', low_memory=False)"
    ]
   },
   {
@@ -6997,25 +2876,19 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "reactome_pathways3 = pandas.read_csv(unprocessed_data_location + 'ChEBI2Reactome_All_Levels.txt',\n",
-    "                                     header=None,\n",
-    "                                     delimiter='\\t',\n",
-    "                                     low_memory=False)\n",
-    "\n",
-    "# remove all non-human pathways\n",
+    "# remove all non-human pathways and save as list\n",
     "reactome_pathways3 = reactome_pathways3.loc[reactome_pathways3[5].apply(lambda x: x == 'Homo sapiens')] \n",
-    "\n",
-    "# save as list\n",
-    "mapped_reactome_identifiers.update({x:set(['PW_0000001']) for x in set(list(reactome_pathways3[1]))})     \n"
+    "reactome_map.update({x:set(['PW_0000001']) for x in set(list(reactome_pathways3[1]))})     "
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**ComPath Reactome Pathway Mappings**  \n",
-    "Use [ComPath Mappings](https://github.com/ComPath/resources/tree/master/mappings) to obtain the following mappings:  \n",
-    "- `Reactome Pathway Identifiers`  ➞ `KEGG Pathway Identifiers` ➞ `Pathway Ontology Identifiers` "
+    "Use [ComPath Mappings](https://github.com/ComPath/resources/tree/master/mappings) to obtain the following mappings:  `Reactome Pathways`  ➞ `KEGG Pathways` ➞ `Pathway Ontology` "
    ]
   },
   {
@@ -7031,22 +2904,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url1 = 'http://compath.scai.fraunhofer.de/export_mappings'\n",
-    "data_downloader(url1, unprocessed_data_location, 'compath_canonical_pathway_mappings.txt')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "compath_cannonical = pandas.read_csv(unprocessed_data_location + 'compath_canonical_pathway_mappings.txt',\n",
-    "                               header=None,\n",
-    "                               delimiter='\\t',\n",
-    "                               low_memory=False)\n",
+    "if not os.path.exists(unprocessed_data_location + 'compath_canonical_pathway_mappings.txt'):\n",
+    "    data_downloader(url1, unprocessed_data_location, 'compath_canonical_pathway_mappings.txt')\n",
     "\n",
-    "# replace NaN with 'None'\n",
+    "# load data\n",
+    "compath_cannonical = pandas.read_csv(unprocessed_data_location + 'compath_canonical_pathway_mappings.txt', header=None, delimiter='\\t', low_memory=False)\n",
     "compath_cannonical.fillna('None', inplace=True)"
    ]
   },
@@ -7057,19 +2921,14 @@
    "outputs": [],
    "source": [
     "for idx, row in tqdm(compath_cannonical.iterrows(), total=compath_cannonical.shape[0]):\n",
-    "    if row[6] == 'kegg' and 'KEGG:' + row[5].strip('path:hsa') in id_mappings.keys() and row[2] == 'reactome':\n",
-    "        for x in id_mappings['KEGG:' + row[5].strip('path:hsa')]:\n",
-    "            if row[1] in mapped_reactome_identifiers.keys():\n",
-    "                mapped_reactome_identifiers[row[1]] |= set([x.split('/')[-1]])\n",
-    "            else:\n",
-    "                mapped_reactome_identifiers[row[1]] = set([x.split('/')[-1]])\n",
-    "    \n",
-    "    if (row[2] == 'kegg' and 'KEGG:' + row[1].strip('path:hsa') in id_mappings.keys()) and row[6] == 'reactome':\n",
-    "        for x in id_mappings['KEGG:' + row[1].strip('path:hsa')]:\n",
-    "            if row[5] in mapped_reactome_identifiers.keys():\n",
-    "                mapped_reactome_identifiers[row[5]] |= set([x.split('/')[-1]])\n",
-    "            else:\n",
-    "                mapped_reactome_identifiers[row[5]] = set([x.split('/')[-1]])         "
+    "    if row[6] == 'kegg' and 'kegg:' + row[5].strip('path:hsa') in id_mappings.keys() and row[2] == 'reactome':\n",
+    "        for x in id_mappings['kegg:' + row[5].strip('path:hsa')]:\n",
+    "            if row[1] in reactome_map.keys(): reactome_map[row[1]] |= set([x.split('/')[-1]])\n",
+    "            else: reactome_map[row[1]] = set([x.split('/')[-1]])\n",
+    "    if (row[2] == 'kegg' and 'kegg:' + row[1].strip('path:hsa') in id_mappings.keys()) and row[6] == 'reactome':\n",
+    "        for x in id_mappings['kegg:' + row[1].strip('path:hsa')]:\n",
+    "            if row[5] in reactome_map.keys(): reactome_map[row[5]] |= set([x.split('/')[-1]])\n",
+    "            else: reactome_map[row[5]] = set([x.split('/')[-1]])         "
    ]
   },
   {
@@ -7085,20 +2944,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url2 = 'https://raw.githubusercontent.com/ComPath/resources/master/mappings/kegg_reactome.csv'\n",
-    "data_downloader(url2, unprocessed_data_location, 'kegg_reactome.csv')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "kegg_reactome_map = pandas.read_csv(unprocessed_data_location + 'kegg_reactome.csv',\n",
-    "                                    header=0,\n",
-    "                                    delimiter=',',\n",
-    "                                    low_memory=False)"
+    "if not os.path.exists(unprocessed_data_location + 'kegg_reactome.csv'):\n",
+    "    data_downloader(url2, unprocessed_data_location, 'kegg_reactome.csv')\n",
+    "\n",
+    "# load data\n",
+    "kegg_reactome_map = pandas.read_csv(unprocessed_data_location + 'kegg_reactome.csv', header=0, delimiter=',', low_memory=False)"
    ]
   },
   {
@@ -7108,29 +2960,24 @@
    "outputs": [],
    "source": [
     "for idx, row in tqdm(kegg_reactome_map.iterrows(), total=kegg_reactome_map.shape[0]):\n",
-    "    if row['Source Resource'] == 'reactome' and 'KEGG:' + row['Target ID'].strip('path:hsa') in id_mappings.keys():\n",
-    "        for x in id_mappings['KEGG:' + row['Target ID'].strip('path:hsa')]:\n",
-    "            if row['Source ID'] in mapped_reactome_identifiers.keys():\n",
-    "                mapped_reactome_identifiers[row['Source ID']] |= set([x.split('/')[-1]])\n",
-    "            else:\n",
-    "                mapped_reactome_identifiers[row['Source ID']] = set([x.split('/')[-1]])\n",
-    "    \n",
-    "    if row['Target Resource'] == 'reactome' and 'KEGG:' + row['Source Resource'].strip('path:hsa') in id_mappings.keys():\n",
-    "        for x in id_mappings['KEGG:' + row['Source ID'].strip('path:hsa')]:\n",
-    "            if row['Target ID'] in mapped_reactome_identifiers.keys():\n",
-    "                mapped_reactome_identifiers[row['Target ID']] |= set([x.split('/')[-1]])\n",
-    "            else:\n",
-    "                mapped_reactome_identifiers[row['Target ID']] = set([x.split('/')[-1]])\n",
-    "    "
+    "    if row['Source Resource'] == 'reactome' and 'kegg:' + row['Target ID'].strip('path:hsa') in id_mappings.keys():\n",
+    "        for x in id_mappings['kegg:' + row['Target ID'].strip('path:hsa')]:\n",
+    "            if row['Source ID'] in reactome_map.keys(): reactome_map[row['Source ID']] |= set([x.split('/')[-1]])\n",
+    "            else: reactome_map[row['Source ID']] = set([x.split('/')[-1]])\n",
+    "    if row['Target Resource'] == 'reactome' and 'kegg:' + row['Source Resource'].strip('path:hsa') in id_mappings.keys():\n",
+    "        for x in id_mappings['kegg:' + row['Source ID'].strip('path:hsa')]:\n",
+    "            if row['Target ID'] in reactome_map.keys(): reactome_map[row['Target ID']] |= set([x.split('/')[-1]])\n",
+    "            else: reactome_map[row['Target ID']] = set([x.split('/')[-1]])"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Reactome Pathway GO Annotation Mappings**  \n",
-    "Use Reactome's [API](https://reactome.org/dev/content-service) to obtain the following mappings:  \n",
-    "- `Reactome Pathway Identifiers`  ➞ `Gene Ontology Identifiers`\n"
+    "Use Reactome's [API](https://reactome.org/dev/content-service) to obtain the following mappings: `Reactome Pathway Identifiers`  ➞ `Gene Ontology Identifiers`\n"
    ]
   },
   {
@@ -7139,24 +2986,20 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "for request_ids in tqdm(list(chunks(list(mapped_reactome_identifiers.keys()), 20))):\n",
-    "    result = content.query_ids(ids=','.join(request_ids))\n",
+    "for request_ids in tqdm(list(chunks(list(reactome_map.keys()), 20))):\n",
+    "    result, key = content.query_ids(ids=','.join(request_ids)), 'goBiologicalProcess'\n",
     "    if result is not None:\n",
     "        for res in result:\n",
-    "            for key in res.keys(): \n",
-    "                if key == 'goBiologicalProcess':\n",
-    "                    for x in res[key]:\n",
-    "                        if res['stId'] in mapped_reactome_identifiers.keys():\n",
-    "                            mapped_reactome_identifiers[res['stId']] |= set(['GO_' + res[key]['accession']])\n",
-    "                        else:\n",
-    "                            mapped_reactome_identifiers[res['stId']] = set(['GO_' + res[key]['accession']])"
+    "            if key in res.keys():\n",
+    "                if res['stId'] in reactome_map.keys(): reactome_map[res['stId']] |= {'GO_' + res[key]['accession']}\n",
+    "                else: reactome_map[res['stId']] = {'GO_' + res[key]['accession']}"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Write Mappings**"
+    "*Write Data*"
    ]
   },
   {
@@ -7165,13 +3008,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# reformat data and write it out\n",
-    "with open(processed_data_location + 'REACTOME_PW_GO_MAPPINGS.txt', 'w') as outfile:\n",
-    "    for key in tqdm(mapped_reactome_identifiers.keys()):\n",
-    "        for mapping in mapped_reactome_identifiers[key]:\n",
-    "            outfile.write(key + '\\t' + mapping + '\\n')\n",
-    "\n",
-    "outfile.close()"
+    "with open(processed_data_location + 'REACTOME_PW_GO_MAPPINGS.txt', 'w') as out:\n",
+    "    for key in tqdm(reactome_map.keys()):\n",
+    "        for x in reactome_map[key]:\n",
+    "            if x.startswith('PW') or x.startswith('GO'): out.write(key + '\\t' + x + '\\n')"
    ]
   },
   {
@@ -7180,20 +3020,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "pw_data = pandas.read_csv(processed_data_location + 'REACTOME_PW_GO_MAPPINGS.txt',\n",
-    "                           header=None,\n",
-    "                           names=['Pathway_IDs', 'Mapping_IDs'],\n",
-    "                           delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "pw_data = pandas.read_csv(processed_data_location + 'REACTOME_PW_GO_MAPPINGS.txt', header=None, names=['Pathway_IDs', 'Mapping_IDs'], delimiter='\\t')\n",
     "\n",
-    "print('There are {edge_count} pathway ontology mappings'.format(edge_count=len(pw_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "print('There are {edge_count} pathway ontology mappings'.format(edge_count=len(pw_data)))\n",
     "pw_data.head(n=5)"
    ]
   },
@@ -7209,13 +3039,13 @@
     "\n",
     "**Data Source Wiki Page:** [Sequence Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources/_edit#sequence-ontology)  \n",
     "\n",
-    "**Purpose:** This script downloads the [genomic identifier mapping](https://www.dropbox.com/s/g0blo27qc8ogvk2/genomic_sequence_ontology_mappings.xlsx?dl=1) file in order to create the following identifier mappings:  \n",
+    "**Purpose:** This script downloads the `genomic_sequence_ontology_mappings.xlsx` file in order to create the following identifier mappings:  \n",
     "- `Gene BioTypes`  ➞ `Sequence Ontology Identifiers`  \n",
     "- `RNA BioTypes`  ➞ `Sequence Ontology Identifiers`  \n",
     "- `variant Types`  ➞ `Sequence Ontology Identifiers`\n",
     "\n",
     "**Output:**  \n",
-    "- [`SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt`](https://www.dropbox.com/s/6dl73a3470u1hcr/SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt?dl=1)\n"
+    "- `SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt`\n"
    ]
   },
   {
@@ -7224,21 +3054,27 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
+    "url='https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/genomic_sequence_ontology_mappings.xlsx'\n",
+    "if not os.path.exists(unprocessed_data_location + 'genomic_sequence_ontology_mappings.xlsx'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
     "mapping_data = pandas.read_excel(open(unprocessed_data_location + 'genomic_sequence_ontology_mappings.xlsx', 'rb'),\n",
-    "                                 sheet_name='GenomicType_SO_Map_09Mar2020',\n",
-    "                                 header=0)\n",
+    "                                 sheet_name='GenomicType_SO_Map_09Mar2020', header=0, engine='openpyxl')\n",
     "\n",
     "# convert data to dictionary\n",
     "genomic_type_so_map = {}\n",
-    "\n",
     "for idx, row in tqdm(mapping_data.iterrows(), total=mapping_data.shape[0]):\n",
-    "    genomic_type_so_map[row['source_*_type'] + '_' + row['Genomic']] = row['SO ID']\n"
+    "    genomic_type_so_map[row['source_*_type'] + '_' + row['Genomic']] = row['SO ID']"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Genes**"
    ]
   },
@@ -7249,18 +3085,9 @@
    "outputs": [],
    "source": [
     "# read in genomic mapping data\n",
-    "genomic_mapped_ids = pickle.load(open(processed_data_location + 'Merged_gene_rna_protein_identifiers.pkl', 'rb'),\n",
-    "                                 encoding='bytes')\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "sequence_map = {}\n",
+    "genomic_mapped_ids = pickle.load(open(processed_data_location + 'Merged_gene_rna_protein_identifiers.pkl', 'rb'))\n",
     "\n",
+    "sequence_map = {}\n",
     "for identifier in tqdm(genomic_mapped_ids.keys()):    \n",
     "    if identifier.startswith('entrez_id_') and identifier.replace('entrez_id_', '') != 'None':\n",
     "        id_clean = identifier.replace('entrez_id_', '')\n",
@@ -7271,27 +3098,22 @@
     "        entrez = [x.replace('entrez_gene_type_', '')  for x in genomic_mapped_ids[identifier] if x.startswith('entrez_gene_type') and x != 'entrez_gene_type_unknown']\n",
     "        \n",
     "        # determine gene type\n",
-    "        if len(ensembl) > 0:\n",
-    "            gene_type = genomic_type_so_map[ensembl[0].replace('ensembl_gene_type_', '') + '_Gene']\n",
-    "        elif len(hgnc) > 0:\n",
-    "            gene_type = genomic_type_so_map[hgnc[0].replace('hgnc_gene_type_', '') + '_Gene']\n",
-    "        elif len(entrez) > 0:\n",
-    "            gene_type = genomic_type_so_map[entrez[0].replace('entrez_gene_type_', '') + '_Gene']\n",
-    "        else:\n",
-    "            gene_type = 'SO_0000704'\n",
+    "        if len(ensembl) > 0: gene_type = genomic_type_so_map[ensembl[0].replace('ensembl_gene_type_', '') + '_Gene']\n",
+    "        elif len(hgnc) > 0: gene_type = genomic_type_so_map[hgnc[0].replace('hgnc_gene_type_', '') + '_Gene']\n",
+    "        elif len(entrez) > 0: gene_type = genomic_type_so_map[entrez[0].replace('entrez_gene_type_', '') + '_Gene']\n",
+    "        else: gene_type = 'SO_0000704'  \n",
     "        \n",
     "        # update sequence map\n",
-    "        if id_clean in sequence_map.keys():\n",
-    "            sequence_map[id_clean] += [gene_type]\n",
-    "        else:\n",
-    "            sequence_map[id_clean] = [gene_type]\n",
-    "    "
+    "        if id_clean in sequence_map.keys(): sequence_map[id_clean] += [gene_type]\n",
+    "        else: sequence_map[id_clean] = [gene_type]"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Transcripts**"
    ]
   },
@@ -7302,47 +3124,30 @@
    "outputs": [],
    "source": [
     "# read in processed Ensembl Transcript data \n",
-    "transcript_data = pandas.read_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt',\n",
-    "                                  header=0,\n",
-    "                                  delimiter='\\t',\n",
-    "                                  low_memory=False)\n",
+    "transcript_data = pandas.read_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt', header=0, delimiter='\\t', low_memory=False)\n",
     "\n",
     "# convert to dictionary\n",
     "transcripts = {}\n",
-    "\n",
     "for idx, row in tqdm(transcript_data.iterrows(), total=transcript_data.shape[0]):\n",
     "    if row['transcript_stable_id'] != 'None':\n",
     "        if row['transcript_stable_id'].replace('transcript_stable_id_', '') in transcripts.keys():\n",
     "            transcripts[row['transcript_stable_id'].replace('transcript_stable_id_', '')] += [row['ensembl_transcript_type']]\n",
-    "        else:\n",
-    "            transcripts[row['transcript_stable_id'].replace('transcript_stable_id_', '')] = [row['ensembl_transcript_type']]\n"
+    "        else: transcripts[row['transcript_stable_id'].replace('transcript_stable_id_', '')] = [row['ensembl_transcript_type']]\n",
+    "            \n",
+    "# update so map dictionary\n",
+    "for identifier in tqdm(transcripts.keys()):\n",
+    "    if transcripts[identifier][0] == 'protein_coding': trans_type = genomic_type_so_map['protein-coding_Transcript']\n",
+    "    elif transcripts[identifier][0] == 'misc_RNA': trans_type = genomic_type_so_map['miscRNA_Transcript']\n",
+    "    else: trans_type = genomic_type_so_map[list(set(transcripts[identifier]))[0] + '_Transcript']\n",
+    "    sequence_map[identifier] = [trans_type, 'SO_0000673']"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "# update SO map dictionary\n",
-    "for identifier in tqdm(transcripts.keys()):\n",
-    "    \n",
-    "    # map transcript type\n",
-    "    if transcripts[identifier][0] == 'protein_coding':\n",
-    "        trans_type = genomic_type_so_map['protein-coding_Transcript']\n",
-    "    elif transcripts[identifier][0] == 'misc_RNA':\n",
-    "        trans_type = genomic_type_so_map['miscRNA_Transcript']\n",
-    "    else:\n",
-    "        trans_type = genomic_type_so_map[list(set(transcripts[identifier]))[0] + '_Transcript']\n",
+    "***\n",
     "\n",
-    "    sequence_map[identifier] = [trans_type, 'SO_0000673']\n",
-    "        "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
     "**Variants**"
    ]
   },
@@ -7355,45 +3160,33 @@
    "outputs": [],
    "source": [
     "# read in variant summary data \n",
-    "# (assumes that is was already downloaded from 'ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz')\n",
-    "variant_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt',\n",
-    "                               header=0,\n",
-    "                               delimiter='\\t',\n",
-    "                               low_memory=False)\n",
+    "url = 'ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'variant_summary.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "    \n",
+    "# load data    \n",
+    "variant_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt', header=0, delimiter='\\t', low_memory=False)\n",
     "\n",
     "# convert to dictionary\n",
     "variants = {}\n",
-    "\n",
     "for idx, row in tqdm(variant_data.iterrows(), total=variant_data.shape[0]):\n",
     "    if row['Assembly'] == 'GRCh38' and row['RS# (dbSNP)'] != -1:\n",
-    "        if 'rs' + str(row['RS# (dbSNP)']) in variants.keys():\n",
-    "                variants['rs' + str(row['RS# (dbSNP)'])] |= set([row['Type']])\n",
-    "        else:\n",
-    "            variants['rs' + str(row['RS# (dbSNP)'])] = set([row['Type']])\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# update SO map dictionary\n",
+    "        if 'rs' + str(row['RS# (dbSNP)']) in variants.keys(): variants['rs' + str(row['RS# (dbSNP)'])] |= set([row['Type']])\n",
+    "        else: variants['rs' + str(row['RS# (dbSNP)'])] = set([row['Type']])\n",
+    "\n",
+    "# update so map dictionary\n",
     "for identifier in tqdm(variants.keys()):\n",
     "    for typ in variants[identifier]:\n",
-    "        # map variant type\n",
     "        var_type = genomic_type_so_map[typ.lower() + '_Variant']\n",
-    "\n",
-    "        if identifier in sequence_map.keys():\n",
-    "            sequence_map[identifier] += [var_type]\n",
-    "        else:\n",
-    "            sequence_map[identifier] = [var_type]\n"
+    "        if identifier in sequence_map.keys(): sequence_map[identifier] += [var_type]\n",
+    "        else: sequence_map[identifier] = [var_type]"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "*** \n",
     "**Write Data**"
    ]
   },
@@ -7409,29 +3202,10 @@
     "        for map_type in sequence_map[key]:\n",
     "            outfile.write(key + '\\t' + map_type + '\\n')\n",
     "\n",
-    "outfile.close()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "so_data = pandas.read_csv(processed_data_location + 'SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt',\n",
-    "                           header=None,\n",
-    "                           names=['Identifier', 'Sequence_Ontology_ID'],\n",
-    "                           delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "so_data = pandas.read_csv(processed_data_location + 'SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt', header=None, delimiter='\\t', names=['Identifier', 'Sequence_Ontology_ID'])\n",
     "\n",
-    "print('There are {edge_count} sequence ontology mappings'.format(edge_count=len(so_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "print('There are {edge_count} sequence ontology mappings'.format(edge_count=len(so_data)))\n",
     "so_data.head(n=5)"
    ]
   },
@@ -7439,6 +3213,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Combine Pathway and Sequence Ontology Mapping Data in Dictionary**  \n",
     "Combine the pathway and sequence mapping data into a dictionary and output it."
    ]
@@ -7449,24 +3225,16 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "subclass_mapping = {}  \n",
-    "\n",
     "# combine genomic and pathway maps\n",
-    "sequence_map.update(mapped_reactome_identifiers)\n",
+    "subclass_mapping = {}  \n",
+    "sequence_map.update(reactome_map)\n",
     "\n",
     "# iterate over pathway lists and combine them\n",
     "for key in tqdm(sequence_map.keys()):\n",
-    "    subclass_mapping[key] = sequence_map[key]\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "    subclass_mapping[key] = sequence_map[key]\n",
+    "\n",
     "# save a copy of the dictionary\n",
-    "pickle.dump(subclass_mapping, open(processed_data_location + 'subclass_construction_map.pkl', 'wb'), protocol=4)\n"
+    "pickle.dump(subclass_mapping, open(construction_approach_location + 'subclass_construction_map.pkl', 'wb'), protocol=4)"
    ]
   },
   {
@@ -7504,22 +3272,8 @@
     "<br>\n",
     "\n",
     "**Output:**  \n",
-    "- Human Protein Ontology ➞ [`human_pro.owl`](https://www.dropbox.com/s/t7sq1dshu2usmkj/human_pro.owl?dl=1)\n",
-    "- Classified Human Protein Ontology (Hermit) ➞ [`human_pro_closed.owl`](https://www.dropbox.com/s/khtyja0zw14rp32/human_pro_closed.owl?dl=1)\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# download ontology using subprocess and OWLTOOLS in order to get the ontology and its imported ontologies\n",
-    "subprocess.check_call(['./pkt_kg/libs/owltools',\n",
-    "                       'http://purl.obolibrary.org/obo/pr.owl',\n",
-    "                       '--merge-import-closure',\n",
-    "                       '-o',\n",
-    "                       unprocessed_data_location + 'pr_with_imports.owl'])"
+    "- Human Protein Ontology ➞ `human_pro.owl`\n",
+    "- Classified Human Protein Ontology (Hermit) ➞ `human_pro_closed.owl`\n"
    ]
   },
   {
@@ -7528,11 +3282,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'pr_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/pr.owl',\n",
+    "                             unprocessed_data_location + 'pr_with_imports.owl'))\n",
+    "    \n",
     "# read in ontology as graph (the ontology is large so this takes ~60 minutes) - 11,858,720 edges on 12/01/2020\n",
-    "graph = Graph()\n",
-    "graph.parse(unprocessed_data_location + 'pr_with_imports.owl')\n",
-    "\n",
-    "print('There are {} axioms in the ontology (date: {})'.format(len(graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
+    "pr_graph = Graph().parse(unprocessed_data_location + 'pr_with_imports.owl')\n",
+    "print('There are {} axioms in the ontology (date: {})'.format(len(pr_graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
   },
   {
@@ -7552,7 +3310,7 @@
     "# convert RDF graph to multidigraph\n",
     "networkx_mdg: networkx.MultiDiGraph = networkx.MultiDiGraph()\n",
     "    \n",
-    "for s, p, o in tqdm(graph):\n",
+    "for s, p, o in tqdm(pr_graph):\n",
     "    networkx_mdg.add_edge(s, o, **{'key': p})"
    ]
   },
@@ -7589,21 +3347,61 @@
     "**NOTE.** Be sure to obtain a new URL from the [ProConsortium](https://proconsortium.org/pro_sparql.shtml) when rebuilding to ensure you are getting the most up-to-date data. This query was last generated on `12/02/2020`."
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "*Approach 1 - Query Loaded Graph to Obtain Human Protein Classes*  \n",
+    "This approach is very slow and should only be used when there is no internet or when the PRO Consortium's SPARQL Endpoint is not functional."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# slower approach when no internet access available\n",
+    "# human_pro_classes = graph.query(\n",
+    "#     \"\"\"SELECT DISTINCT ?PRO_term\n",
+    "#            WHERE {\n",
+    "#               ?PRO_term rdf:type owl:Class .\n",
+    "#               ?PRO_term rdfs:subClassOf ?restriction .\n",
+    "#               ?restriction owl:onProperty obo:RO_0002160 .\n",
+    "#               ?restriction owl:someValuesFrom obo:NCBITaxon_9606 .}\n",
+    "#            \"\"\", initNs={'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',\n",
+    "#                         'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',\n",
+    "#                         'owl': 'http://www.w3.org/2002/07/owl#',\n",
+    "#                         'obo': 'http://purl.obolibrary.org/obo/'}) \n",
+    "\n",
+    "# print('There are {} edges in the ontology (date:{})'.format(len(human_pro_classes), datetime.datetime.now().strftime('%m/%d/%Y')))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "*Approach 2 - Query PRO Consortium SPARQL Endpoint to Obtain Human Protein Classes*  \n",
+    "This approach is much faster than Approach 1 and should be used in place of it,"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# download data - pro classes only_in_taxon some Homo sapiens (61,071 classes on 12/01/2020)\n",
+    "# download data\n",
     "url = 'https://sparql.proconsortium.org/virtuoso/sparql?query=PREFIX+obo%3A+%3Chttp%3A%2F%2Fpurl.obolibrary.org%2Fobo%2F%3E%0D%0A%0D%0ASELECT+%3FPRO_term%0D%0AFROM+%3Chttp%3A%2F%2Fpurl.obolibrary.org%2Fobo%2Fpr%3E%0D%0AWHERE+%7B%0D%0A+++++++%3FPRO_term+rdf%3Atype+owl%3AClass+.%0D%0A+++++++%3FPRO_term+rdfs%3AsubClassOf+%3Frestriction+.%0D%0A+++++++%3Frestriction+owl%3AonProperty+obo%3ARO_0002160+.%0D%0A+++++++%3Frestriction+owl%3AsomeValuesFrom+obo%3ANCBITaxon_9606+.%0D%0A%0D%0A+++++++%23+use+this+to+filter-out+things+like+hgnc+ids%0D%0A+++++++FILTER+%28regex%28%3FPRO_term%2C%22http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2F*%22%29%29+.%0D%0A%7D&format=text%2Fhtml&debug='\n",
-    "html = requests.get(url, allow_redirects=True).content\n",
+    "if not os.path.exists(unprocessed_data_location + 'human_pro_classes.html'):\n",
+    "    data_downloader(url, unprocessed_data_location, 'human_pro_classes.html')\n",
     "\n",
-    "# extract data from html table\n",
-    "df_list = pandas.read_html(html)\n",
-    "human_pro_classes = list(df_list[-1]['PRO_term'])\n",
+    "# load data\n",
+    "df_list = pandas.read_html(unprocessed_data_location + 'human_pro_classes.html')\n",
     "\n",
-    "print('There are {} edges in the ontology (date:{})'.format(len(human_pro_classes), datetime.datetime.now().strftime('%m/%d/%Y')))\n"
+    "# extract data from html table - pro classes only_in_taxon some Homo sapiens (68,929 classes on 12/01/2020)\n",
+    "human_pro_classes = list(df_list[-1]['PRO_term'])\n",
+    "print('There are {} edges in the ontology (date:{})'.format(len(human_pro_classes), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
   },
   {
@@ -7631,27 +3429,14 @@
     "    # add edges from forward and reverse bfs paths\n",
     "    for path in forward + reverse:\n",
     "        human_pro_graph.add((path[0], path[2], path[1]))\n",
-    "        human_networkx_mdg.add_edge(path[0], path[1], path[2])"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# verify that the constructed ontology only has 1 component\n",
-    "networkx.number_connected_components(human_networkx_mdg.to_undirected())"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# save filtered ontology\n",
-    "human_pro_graph.serialize(destination=unprocessed_data_location + 'human_pro.owl', format='xml')"
+    "        human_networkx_mdg.add_edge(path[0], path[1], path[2])\n",
+    "\n",
+    "# save data\n",
+    "if networkx.number_connected_components(human_networkx_mdg.to_undirected()) == 1:\n",
+    "    human_pro_graph.serialize(destination=unprocessed_data_location + 'human_pro.owl', format='xml')\n",
+    "    ontology_file_formatter(unprocessed_data_location, 'human_pro.owl', self.owltools_location)\n",
+    "else:\n",
+    "    raise ValueError('Human PRO Contains >1 Connected Component')"
    ]
   },
   {
@@ -7678,13 +3463,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# # run reasoner -- RUN FROM COMMAND LINE NOT HERE\n",
-    "subprocess.run(['./pkt_kg/libs/owltools',\n",
-    "                './resources/unprocessed_data/human_pro_filtered.owl',\n",
-    "                '--reasoner elk',\n",
-    "                '--run-reasoner',\n",
-    "                '--assert-implied',\n",
-    "                '-o ./resources/processed_data/human_pro_closed.owl'])"
+    "# run reasoner\n",
+    "command = \"./pkt_kg/libs/owltools {} --reasoner {} --run-reasoner --assert-implied -o {}\"\n",
+    "os.system(command.format(unprocessed_data_location + 'human_pro.owl', 'elk',\n",
+    "                         ontology_data_location + 'pr_with_imports.owl'))\n",
+    "\n",
+    "# fix file formatting\n",
+    "ontology_file_formatter(ontology_data_location, '/pr_with_imports.owl', self.owltools_location)"
    ]
   },
   {
@@ -7701,7 +3486,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "gets_ontology_statistics(processed_data_location + 'human_pro_closed.owl')"
+    "gets_ontology_statistics(ontology_data_location + 'pr_with_imports.owl')"
    ]
   },
   {
@@ -7719,36 +3504,8 @@
     "**Purpose:** This script downloads the [ro.owl](http://purl.obolibrary.org/obo/ro.owl) file from [obofoundry.org](http://www.obofoundry.org/) in order to obtain all `ObjectProperties` and their inverse relations.  \n",
     "\n",
     "**Output:** \n",
-    "- Relations and Inverse Relations ➞ [`INVERSE_RELATIONS.txt`](https://www.dropbox.com/s/4gq1iebdxta7qr8/INVERSE_RELATIONS.txt?dl=1)\n",
-    "- Relations and Labels ➞ [`RELATIONS_LABELS.txt`](https://www.dropbox.com/s/vr0tj22am192ubw/RELATIONS_LABELS.txt?dl=1)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Download Ontology_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# download ontology using subprocess and OWLTOOLS in order to get the ontology and its imported ontologies\n",
-    "subprocess.run(['./pkt_kg/libs/owltools',\n",
-    "                'http://purl.obolibrary.org/obo/ro.owl',\n",
-    "                '--merge-import-closure',\n",
-    "                '-o',\n",
-    "                unprocessed_data_location + 'ro_with_imports.owl'])"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Load Ontology to RDFLib Graph_"
+    "- Relations and Inverse Relations ➞ `INVERSE_RELATIONS.txt`\n",
+    "- Relations and Labels ➞ `RELATIONS_LABELS.txt`"
    ]
   },
   {
@@ -7757,9 +3514,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ro_graph = Graph()\n",
-    "ro_graph.parse(unprocessed_data_location + 'ro_with_imports.owl')\n",
-    "\n",
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'ro_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/ro.owl',\n",
+    "                             unprocessed_data_location + 'ro_with_imports.owl'))\n",
+    "# load graph\n",
+    "ro_graph = Graph().parse(unprocessed_data_location + 'ro_with_imports.owl')\n",
     "print('There are {} edges in the ontology (date:{})'.format(len(ro_graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
   },
@@ -7767,6 +3528,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Identify Relations and Inverse Relations**  \n",
     "Identify all relations and their inverse relations using the `owl:inverseOf` property. To make it easier to look up the inverse relations, each pair is listed twice, for example:  \n",
     "- [location of](http://www.ontobee.org/ontology/RO?iri=http://purl.obolibrary.org/obo/RO_0001015) `owl:inverseOf` [located in](http://www.ontobee.org/ontology/RO?iri=http://purl.obolibrary.org/obo/RO_0001025)  \n",
@@ -7780,18 +3543,12 @@
    "outputs": [],
    "source": [
     "with open(relations_data_location + 'INVERSE_RELATIONS.txt', 'w') as outfile:\n",
-    "    \n",
-    "    # write column names\n",
     "    outfile.write('Relation' + '\\t' + 'Inverse_Relation' + '\\n')\n",
-    "\n",
-    "    # find inverse relations\n",
     "    for s, p, o in tqdm(ro_graph):\n",
     "        if 'owl#inverseOf' in str(p):\n",
     "            if 'RO' in str(s) and 'RO' in str(o):\n",
     "                outfile.write(str(s.split('/')[-1]) + '\\t' + str(o.split('/')[-1]) + '\\n')\n",
-    "                outfile.write(str(o.split('/')[-1]) + '\\t' + str(s.split('/')[-1]) + '\\n')\n",
-    "\n",
-    "outfile.close()"
+    "                outfile.write(str(o.split('/')[-1]) + '\\t' + str(s.split('/')[-1]) + '\\n')"
    ]
   },
   {
@@ -7807,19 +3564,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ro_data = pandas.read_csv(relations_data_location + 'INVERSE_RELATIONS.txt',\n",
-    "                          header=0,\n",
-    "                          delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "ro_data = pandas.read_csv(relations_data_location + 'INVERSE_RELATIONS.txt', header=0, delimiter='\\t')\n",
     "\n",
-    "print('There are {edge_count} RO Relations and Inverse Relations'.format(edge_count=len(ro_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "print('There are {edge_count} RO Relations and Inverse Relations'.format(edge_count=len(ro_data)))\n",
     "ro_data.head(n=5)"
    ]
   },
@@ -7827,6 +3575,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "***\n",
+    "\n",
     "**Get Relations Labels**  \n",
     "Identify all relations and their labels for use when building the knowledge graph."
    ]
@@ -7837,30 +3587,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "results = ro_graph.query(\n",
-    "    \"\"\"SELECT DISTINCT ?p ?p_label\n",
-    "           WHERE {\n",
-    "              ?p rdf:type owl:ObjectProperty .\n",
-    "              ?p rdfs:label ?p_label . }\n",
-    "           \"\"\", initNs={\"rdf\": 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',\n",
-    "                        \"rdfs\": 'http://www.w3.org/2000/01/rdf-schema#',\n",
-    "                        \"owl\": 'http://www.w3.org/2002/07/owl#'})    "
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "results = {str(x[2]).lower(): str(x[0]) for x in ro_graph if '/RO_' in str(x[0]) and 'label' in str(x[1]).lower()}\n",
+    "\n",
     "# write data to file\n",
     "with open(relations_data_location + 'RELATIONS_LABELS.txt', 'w') as outfile:\n",
-    "    \n",
-    "    # write column names\n",
     "    outfile.write('Relation' + '\\t' + 'Label' + '\\n')\n",
-    "\n",
-    "    for p, p_label in list(results):\n",
-    "        outfile.write(str(p).split('/')[-1] + '\\t' + str(p_label) + '\\n')"
+    "    for k, v in results.items():\n",
+    "        outfile.write(str(v).split('/')[-1] + '\\t' + str(k) + '\\n')"
    ]
   },
   {
@@ -7876,19 +3609,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ro_data_label = pandas.read_csv(relations_data_location + 'RELATIONS_LABELS.txt',\n",
-    "                                header=0,\n",
-    "                                delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "ro_data_label = pandas.read_csv(relations_data_location + 'RELATIONS_LABELS.txt', header=0, delimiter='\\t')\n",
     "\n",
-    "print('There are {edge_count} RO Relations and Labels'.format(edge_count=len(ro_data_label)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "print('There are {edge_count} RO Relations and Labels'.format(edge_count=len(ro_data_label)))\n",
     "ro_data_label.head(n=5)"
    ]
   },
@@ -7918,12 +3642,12 @@
     "\n",
     "**Data Source Wiki Page:** [Clinvar](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#clinvar)  \n",
     "\n",
-    "**Purpose:** This script downloads the [variant_summary.txt](ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz) file from [CLinVar](https://www.ncbi.nlm.nih.gov/clinvar/) in order to create the following edges:  \n",
+    "**Purpose:** This script downloads the [variant_summary.txt](ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz) file from [ClinVar](https://www.ncbi.nlm.nih.gov/clinvar/) in order to create the following edges:  \n",
     "- gene-variant  \n",
     "- variant-disease  \n",
     "- variant-phenotype  \n",
     "\n",
-    "**Output:** [`CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt`](https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1)\n"
+    "**Output:** `CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt`\n"
    ]
   },
   {
@@ -7932,20 +3656,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz'\n",
-    "data_downloader(url, unprocessed_data_location)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "clinvar_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt',\n",
-    "                               header=0,\n",
-    "                               delimiter='\\t',\n",
-    "                               low_memory=False)"
+    "if not os.path.exists(unprocessed_data_location + 'variant_summary.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "clinvar_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt', header=0, delimiter='\\t', low_memory=False)"
    ]
   },
   {
@@ -7973,36 +3690,10 @@
     "explode_df_clinvar['PhenotypeIDS'].replace('Human Phenotype Ontology:HP:','HP_', inplace=True, regex=True)\n",
     "\n",
     "# write data\n",
-    "explode_df_clinvar.to_csv(processed_data_location + 'CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt',\n",
-    "                          header=True,\n",
-    "                          sep='\\t',\n",
-    "                          encoding='utf-8',\n",
-    "                          index=False)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preview Processed Data_"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "print('There are {edge_count} variant edges'.format(edge_count=len(explode_df_clinvar)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# preview data\n",
+    "explode_df_clinvar.to_csv(processed_data_location + 'CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt', header=True, sep='\\t', encoding='utf-8', index=False)\n",
+    "\n",
+    "# print row count and preview data\n",
+    "print('There are {edge_count} variant edges'.format(edge_count=len(explode_df_clinvar)))\n",
     "explode_df_clinvar.head(n=5)"
    ]
   },
@@ -8037,168 +3728,58 @@
     "<br>\n",
     "\n",
     "**Output:**  \n",
-    "- protein-cofactor ➞ [`UNIPROT_PROTEIN_COFACTOR.txt`](https://www.dropbox.com/s/xjtljf21eqign73/UNIPROT_PROTEIN_COFACTOR.txt?dl=1)\n",
-    "- protein-catalyst ➞ [`UNIPROT_PROTEIN_CATALYST.txt`](https://www.dropbox.com/s/w4lh6k9wbo5qkw0/UNIPROT_PROTEIN_CATALYST.txt?dl=1)\n"
+    "- protein-cofactor ➞ `UNIPROT_PROTEIN_COFACTOR.txt`\n",
+    "- protein-catalyst ➞ `UNIPROT_PROTEIN_CATALYST.txt`\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 67,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Downloading Data from https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Creviewed%2Centry%20name%2Cdatabase(PRO)%2Cchebi(Cofactor)%2Cchebi(Catalytic%20activity)&format=tab\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Creviewed%2Centry%20name%2Cdatabase(PRO)%2Cchebi(Cofactor)%2Cchebi(Catalytic%20activity)&format=tab'\n",
-    "data_downloader(url, unprocessed_data_location, 'uniprot-cofactor-catalyst.tab')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 68,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "100%|██████████| 194238/194238 [00:00<00:00, 342529.71it/s]\n"
-     ]
-    }
-   ],
-   "source": [
+    "if not os.path.exists(unprocessed_data_location + 'uniprot-cofactor-catalyst.tab'):\n",
+    "    data_downloader(url, unprocessed_data_location, 'uniprot-cofactor-catalyst.tab')\n",
+    "\n",
+    "# upload datta\n",
     "data = open(unprocessed_data_location + 'uniprot-cofactor-catalyst.tab').readlines()\n",
     "\n",
     "# reformat data and write it out\n",
     "with open(processed_data_location + 'UNIPROT_PROTEIN_COFACTOR.txt', 'w') as outfile1, open(processed_data_location + 'UNIPROT_PROTEIN_CATALYST.txt', 'w') as outfile2:\n",
     "    for line in tqdm(data):\n",
-    "\n",
     "        # get cofactors\n",
     "        if 'CHEBI' in line.split('\\t')[4]: \n",
     "            for i in line.split('\\t')[4].split(';'):\n",
     "                chebi = i.split('[')[-1].replace(']', '').replace(':', '_')\n",
     "                outfile1.write('PR_' + line.split('\\t')[3].strip(';') + '\\t' + chebi + '\\n')\n",
-    "        \n",
     "        # get catalysts\n",
     "        if 'CHEBI' in line.split('\\t')[5]:       \n",
     "            for i in line.split('\\t')[5].split(';'):\n",
     "                chebi = i.split('[')[-1].replace(']', '').replace(':', '_')\n",
-    "                outfile2.write('PR_' + line.split('\\t')[3].strip(';') + '\\t' + chebi + '\\n')\n",
-    "\n",
-    "outfile1.close()\n",
-    "outfile2.close()"
+    "                outfile2.write('PR_' + line.split('\\t')[3].strip(';') + '\\t' + chebi + '\\n')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Preview Cofactor Data_"
+    "***\n",
+    "\n",
+    "**Cofactor Data**  "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 69,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 10175 protein-cofactor edges\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "pcp1_data = pandas.read_csv(processed_data_location + 'UNIPROT_PROTEIN_COFACTOR.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Protein_Ontology_IDs', 'CHEBI_IDs'],\n",
-    "                            delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "pcp1_data = pandas.read_csv(processed_data_location + 'UNIPROT_PROTEIN_COFACTOR.txt', header=None, names=['Protein_Ontology_IDs', 'CHEBI_IDs'], delimiter='\\t')\n",
     "\n",
-    "print('There are {edge_count} protein-cofactor edges'.format(edge_count=len(pcp1_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 70,
-   "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>Protein_Ontology_IDs</th>\n",
-       "      <th>CHEBI_IDs</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>PR_Q9Y2J0</td>\n",
-       "      <td>CHEBI_29108</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_597326</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>PR_P04179</td>\n",
-       "      <td>CHEBI_29035</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>PR_Q9H6E5</td>\n",
-       "      <td>CHEBI_18420</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>PR_Q9H6E5</td>\n",
-       "      <td>CHEBI_29035</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Protein_Ontology_IDs     CHEBI_IDs\n",
-       "0            PR_Q9Y2J0   CHEBI_29108\n",
-       "1            PR_Q96I15  CHEBI_597326\n",
-       "2            PR_P04179   CHEBI_29035\n",
-       "3            PR_Q9H6E5   CHEBI_18420\n",
-       "4            PR_Q9H6E5   CHEBI_29035"
-      ]
-     },
-     "execution_count": 70,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} protein-cofactor edges'.format(edge_count=len(pcp1_data)))\n",
     "pcp1_data.head(n=5)"
    ]
   },
@@ -8206,106 +3787,22 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "_Preview Catalyst Data_"
+    "***\n",
+    "\n",
+    "\n",
+    "**Catalyst Data**  "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 71,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "There are 89015 protein-catalyst edges\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "pcp2_data = pandas.read_csv(processed_data_location + 'UNIPROT_PROTEIN_CATALYST.txt',\n",
-    "                            header=None,\n",
-    "                            names=['Protein_Ontology_IDs', 'CHEBI_IDs'],\n",
-    "                            delimiter='\\t')\n",
+    "# load data, print row count, and preview it\n",
+    "pcp2_data = pandas.read_csv(processed_data_location + 'UNIPROT_PROTEIN_CATALYST.txt', header=None, names=['Protein_Ontology_IDs', 'CHEBI_IDs'], delimiter='\\t')\n",
     "\n",
-    "print('There are {edge_count} protein-catalyst edges'.format(edge_count=len(pcp2_data)))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 72,
-   "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>Protein_Ontology_IDs</th>\n",
-       "      <th>CHEBI_IDs</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_15378</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_57843</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_57972</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_29317</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>PR_Q96I15</td>\n",
-       "      <td>CHEBI_13193</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "  Protein_Ontology_IDs    CHEBI_IDs\n",
-       "0            PR_Q96I15  CHEBI_15378\n",
-       "1            PR_Q96I15  CHEBI_57843\n",
-       "2            PR_Q96I15  CHEBI_57972\n",
-       "3            PR_Q96I15  CHEBI_29317\n",
-       "4            PR_Q96I15  CHEBI_13193"
-      ]
-     },
-     "execution_count": 72,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
+    "print('There are {edge_count} protein-catalyst edges'.format(edge_count=len(pcp2_data)))\n",
     "pcp2_data.head(n=5)"
    ]
   },
@@ -8317,57 +3814,28 @@
     "\n",
     "***\n",
     "***\n",
-    "### INSTANCE AND/OR SUBCLASS METADATA <a class=\"anchor\" id=\"create-instance-metadata\"></a>\n",
+    "### INSTANCE AND/OR SUBCLASS (NON-ONTOLOGY CLASS) METADATA <a class=\"anchor\" id=\"create-instance-metadata\"></a>\n",
     "***\n",
     "\n",
-    "\n",
-    "### *!! Run this Step after Creating the Edge List, but Before Building the Knowledge Graph!!*\n",
-    "\n",
-    "<br>\n",
-    "\n",
     "**Data Source Wiki Page:** [Dependencies](https://github.com/callahantiff/PheKnowLator/wiki/Dependencies/#node-metadata) \n",
     "\n",
-    "<br>\n",
-    "\n",
-    "**Purpose:** The goal of this section is to obtain metadata for each instance and/or subclass data source used in the knowledge graph. To determine which of the edges contains instance and/or subclass data, the [`Master_Edge_List_Dict.json`](https://www.dropbox.com/s/w4l9yffnn4tyk2e/Master_Edge_List_Dict.json?dl=1) file is parsed and saved to a nested dictionary (see example below). \n",
-    "\n",
-    "```python\n",
-    "{\n",
-    "  'complex': {\n",
-    "              'chemical-complex': [[node_1, node_2]...[node_n, node_m]],\n",
-    "              'complex-complex':  [[node_1, node_2]...[node_n, node_m]],\n",
-    "              'complex-pathway':  [[node_1, node_2]...[node_n, node_m]],\n",
-    "              },\n",
-    "     'gene': {\n",
-    "                'chemical-gene':  [[node_1, node_2]...[node_n, node_m]],\n",
-    "                 'gene-disease':  [[node_1, node_2]...[node_n, node_m]],\n",
-    "              }\n",
-    "}\n",
-    "```\n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "Once this dictionary is created, each major data type (examples shown in the list below) will be processed. For **[`Release V2.0.0`](https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0)**, the following are instance and/or subclass data and require the compiling of metadata:\n",
+    "**Purpose:** The goal of this section is to obtain metadata for each non-ontology instance and/or subclass data source and all relations used in the knowledge graph. For **[`Release V2.0.0`](https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0)**, the following are non-ontology instance and/or subclass data and require the compiling of metadata:\n",
     "- [Genes](#gene-metadata)\n",
     "- [RNA](#rna-metadata)\n",
+    "- [Variants](#variant-metadata)  \n",
     "- [Pathways](#pathway-metadata)\n",
-    "- [Variants](#variant-metadata)\n",
-    "\n",
+    "- [Relations](#relations-metadata)\n",
     "\n",
     "<br>\n",
     "\n",
-    "____\n",
-    "\n",
     "**Metadata:** The <u>metadata</u> we will gather includes:  \n",
     "\n",
     "| **Metadata Type** | **Definition** | **Example Node**  | **Example Node Metadata** | \n",
     "| :---: | :---: | :---: | :---: | \n",
     "| Label | The primary label or name for the node | `R-HSA-1006173` | \"CFH:Host cell surface\" |       \n",
-    "| Description | A definition or other useful details about the node | `rs794727058` | This `germline` `single nucleotide variant (allele alteration: C➞T)` located on chromosome `5 (GRCh38: NC_000005.10, start/stop positions (126555930/126555930))` with `pathogenic` clinical significance and a last review date of `2/23/2015` (review status: `criteria provided, single submitter`). |        \n",
+    "| Description | A definition or other useful details about the node | `rs794727058` | This `germline` `single nucleotide variant` located on chromosome `5 (GRCh38: NC_000005.10, start/stop positions (126555930/126555930))` with `pathogenic` clinical significance and a last review date of `2/23/2015` (review status: `criteria provided, single submitter`). |        \n",
     "| Synonym | Alternative terms used for a node | `81399` | \"OR1-1, OR7-21\" |           \n",
     "\n",
-    "<br>\n",
-    "\n",
     "The metadata information will be used to create the following edges in the knowledge graph:  \n",
     "- **Label** ➞ node `rdfs:label`  \n",
     "- **Description** ➞ node `obo:IAO_0000115` description \n",
@@ -8375,35 +3843,26 @@
     "\n",
     "<br>\n",
     "\n",
-    "*<b>NOTE.</b> All node metadata datasets are written to the `node_data` directory. The algorithm will look for data in this directory and if it is not there, then no node metadata will be created.*\n",
+    "*<b>NOTE.</b> All node metadata are written to the `node_data` directory as a `pickled` dictionary called `node_metadata_dict.pkl`. The algorithm will look for this dictionary in the `node_data` directory and if it is not there, then no node metadata will be created.*\n",
+    "\n",
+    "<br>\n",
     "\n",
-    "_____"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
     "### Prepare Metadata Dictionaries\n",
     "***\n",
     "\n",
     "**Purpose:** To create the resources needed in order to create metadata dictionaries, which are in turn used to obtain metadata for instance and/or subclass data nodes. This process has the following steps:\n",
     "\n",
-    "**1. [Identify Instance and/or Subclass Data Nodes](#identify-instance-or-subclass-data-nodes):** In order to automatically obtain the list of edges that include an instance data source and their corresponding edge lists, the `Master_Edge_List_Dict.json` is read in and processed.  \n",
-    "  - <u>Input Data</u>: [`Master_Edge_List_Dict.json`](https://www.dropbox.com/s/w4l9yffnn4tyk2e/Master_Edge_List_Dict.json?dl=1)  \n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**2. [Generate Metadata Dictionaries](#generate-metadata-dictionaries):** In order to efficiently obtain metadata for the instance and/or subclass data nodes identified in _Step 1_, we first read in the data for each node type (i.e. genes, rna, pathways, and variants) and convert it into a dictionary. Then, each metadata dictionary is saved to a `master_metadata_dictionary`, keyed by node type.\n",
+    "**1. [Generate Metadata Dictionaries](#generate-metadata-dictionaries):** In order to efficiently obtain metadata for all non-ontology instance and/or subclass data nodes and all relations, we first read in the data for each type (i.e. genes, rna, pathways, variants, and relations) and convert them into a dictionary. Then, each metadata dictionary is merged together and saved to a `master_metadata_dictionary`, keyed by identifier.\n",
     "  - <u>Input Datasets</u>:  \n",
-    "    - Genes ➞ [`Homo_sapiens.gene_info`](https://www.dropbox.com/s/95jmr5bqkjcft8k/Homo_sapiens.gene_info?dl=1)    \n",
-    "    - RNA ➞ [`ensembl_identifier_data_cleaned.txt`](https://www.dropbox.com/s/gcq8157cqcdz2d0/ensembl_identifier_data_cleaned.txt?dl=1) \n",
-    "    - Pathways ➞ [`reactome2py API`](https://github.com/reactome/reactome2py)   \n",
-    "    - Variants ➞ [`variant_summary.txt.gz`](https://www.dropbox.com/s/nqsgf92jhu7690e/variant_summary.txt?dl=1)  \n",
+    "    - Genes ➞ `Homo_sapiens.gene_info`   \n",
+    "    - RNA ➞ `ensembl_identifier_data_cleaned.txt` \n",
+    "    - Pathways ➞ [`reactome2py API`](https://github.com/reactome/reactome2py) ; `ReactomePathways.txt`    \n",
+    "    - Variants ➞ `variant_summary.txt`  \n",
+    "    - Relations ➞ `ro_with_imports.owl`  \n",
     "\n",
     "<br>\n",
     "\n",
-    "**3. [Write Metadata Files](#write-metadata-files):** The Instance and/or subclass data node dictionary from _Step 1_ and metadata dictionaries from _Step 2_ are used to write `.txt` files for all `edge-type` data included in the instance and/or subclass node dictionary.\n",
+    "**2. [Write Metadata Files](#write-metadata-files):** The `master_metadata_dictionary` dictionary from _Step 1_ is `pickled` and saved to the `resources/node_data/` directory.\n",
     "\n",
     "<br>\n",
     "\n",
@@ -8414,89 +3873,19 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Identify Instance and/or Subclass Data Nodes  <a class=\"anchor\" id=\"identify-instance-or-subclass-data-nodes\"></a>"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# read in data files for each edge type\n",
-    "edge_data = json.load(open('./resources/Master_Edge_List_Dict.json', 'r'))\n",
-    "edge_dict = {key:[edge_data[key]['data_type'], edge_data[key]['edge_list']] for key in edge_data.keys()}\n"
+    "### Generate Metadata Dictionaries  <a class=\"anchor\" id=\"generate-metadata-dictionaries\"></a>\n",
+    "In this step, the goal is to create a metadata dictionary for each node type that does not rely on API data. In this case, only the **Gene**, **RNA**, and **Variant** nodes require data that is not from an API.\n"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Sort Data**  \n",
-    "For all edges in the `edge_dict()` that include instance and/or subclass data, we create a new dictionary where each edge type is further organized by node from the edge type that references the instance data (e.g. from the `chemical-gene` edge type, the `gene` node references instance data)."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# sort data files\n",
-    "metadata_file_info = {}\n",
+    "***\n",
     "\n",
-    "for edge in tqdm(edge_dict.keys()): \n",
-    "    if 'entity' in edge_dict[edge][0]:\n",
-    "        \n",
-    "        # get instance type\n",
-    "        inst_type = edge.split('-')[edge_dict[edge][0].split('-').index('entity')]\n",
-    "        \n",
-    "        # read in data\n",
-    "        if inst_type in metadata_file_info.keys(): \n",
-    "            metadata_file_info[inst_type][edge] = {}\n",
-    "            metadata_file_info[inst_type][edge]['data'] = edge_dict[edge][1]\n",
-    "            metadata_file_info[inst_type][edge]['inst_sbc_idx'] = edge_dict[edge][0]\n",
-    "        else:\n",
-    "            metadata_file_info[inst_type] = {}\n",
-    "            metadata_file_info[inst_type][edge] =  {}\n",
-    "            metadata_file_info[inst_type][edge]['data'] =  edge_dict[edge][1]\n",
-    "            metadata_file_info[inst_type][edge]['inst_sbc_idx'] = edge_dict[edge][0]\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Generate Metadata Dictionaries  <a class=\"anchor\" id=\"generate-metadata-dictionaries\"></a>\n",
-    "In this step, the goal is to create a metadata dictionary for each node type that does not rely on API data. In this case, only the **Gene**, **RNA**, and **Variant** nodes require data that is not from an API.\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Genes Metadata Dictionary**"
+    "#### Genes Metadata Dictionary <a class=\"anchor\" id=\"gene-metadata\"></a>\n",
+    "\n",
+    "The nested dictionary of gene metadata is created by looping over the merged data described in the prior column. The `keys` of the dictionary are `Entrez gene identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -8506,25 +3895,14 @@
    "outputs": [],
    "source": [
     "# entrez gene data\n",
-    "entrez_gene_data = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.gene_info',\n",
-    "                                   header=0,\n",
-    "                                   delimiter='\\t',\n",
-    "                                   low_memory=False)\n",
+    "entrez_gene_data = pandas.read_csv(unprocessed_data_location + 'Homo_sapiens.gene_info', header=0, delimiter='\\t', low_memory=False)\n",
     "\n",
     "# remove all rows that are not human\n",
     "entrez_gene_data = entrez_gene_data.loc[entrez_gene_data['#tax_id'].apply(lambda x: x == 9606)]\n",
     "\n",
     "# replace NaN and '-' with 'None'\n",
     "entrez_gene_data.fillna('None', inplace=True)\n",
-    "entrez_gene_data.replace('-','None', inplace=True, regex=False)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Create Gene Metadata Dictionary_  \n",
-    "The nested dictionary of gene metadata is created by looping over the merged data described in the prior column. The `keys` of the dictionary are `Entrez gene identifiers` and the `values` are dictionaries for each metadata type: `symbol`, `description`, and `name`."
+    "entrez_gene_data.replace('-','None', inplace=True, regex=False)"
    ]
   },
   {
@@ -8534,90 +3912,42 @@
    "outputs": [],
    "source": [
     "# create metadata\n",
-    "genes, label, description, synonym = [], [], [], []\n",
-    "\n",
+    "genes, lab, desc, syn = [], [], [], []\n",
     "for idx, row in tqdm(entrez_gene_data.iterrows(), total=entrez_gene_data.shape[0]):\n",
-    "    # node \n",
-    "    if row['GeneID'] != 'None':\n",
-    "        genes.append(row['GeneID'])\n",
-    "    \n",
-    "    # label -- only want metadata if there is a label\n",
-    "    if row['Symbol'] != 'None' or row['Symbol'] != '':       \n",
-    "        label.append(row['Symbol'])\n",
-    "    else:\n",
-    "        label.append('Entrez_ID:' + row['GeneID'])\n",
-    "\n",
-    "    # description        \n",
-    "    if row['Full_name_from_nomenclature_authority'] != 'None' and row['type_of_gene'] != 'None' and row['chromosome'] != 'None' and row['map_location'] != 'None':\n",
-    "\n",
-    "        description.append(\"{desc} has locus group '{gene}' and is located on chromosome {chrom} (map_location: {map_loc}).\".format(desc=row['Symbol'],\n",
-    "                                                                                                                             gene=row['type_of_gene'],\n",
-    "                                                                                                                             chrom=row['chromosome'],\n",
-    "                                                                                                                             map_loc=row['map_location']))\n",
-    "\n",
-    "    else:\n",
-    "        description.append(\"{desc} locus group '{gene}'.\".format(desc=row['Symbol'], gene=row['type_of_gene']))\n",
-    "\n",
-    "    # synonym        \n",
-    "    if row['Synonyms'] != 'None' and row['Other_designations'] != 'None':\n",
-    "        syns = '|'.join(set([x for x in (row['Synonyms'] + row['Other_designations']).split('|') if x != 'None' or x != '']))\n",
-    "        synonym.append(syns)\n",
-    "    elif row['Synonyms'] != 'None':\n",
-    "        syns = '|'.join(set([x for x in (row['Synonyms']).split('|') if x != 'None' or x != '']))\n",
-    "        synonym.append(syns)\n",
-    "    elif row['Other_designations'] != 'None':\n",
-    "        syns = '|'.join(set([x for x in (row['Other_designations']).split('|') if x != 'None' or x != '']))\n",
-    "        synonym.append(syns)\n",
-    "    else:\n",
-    "        synonym.append('None')\n",
-    "            \n",
-    "    \n",
-    "# combine into new data frame        \n",
-    "gene_metadata_final = pandas.DataFrame(list(zip(genes, label, description, synonym)), columns =['ID', 'Label', 'Description', 'Synonym'])\n",
+    "    gene_id, sym, defn, gene_type = row['GeneID'], row['Symbol'], row['description'], row['type_of_gene']\n",
+    "    chrom, map_loc, s1, s2 = row['chromosome'], row['map_location'], row['Synonyms'], row['Other_designations']\n",
+    "    if gene_id != 'None':\n",
+    "        genes.append('http://www.ncbi.nlm.nih.gov/gene/' + str(gene_id))\n",
+    "        if sym != 'None' or sym != '': lab.append(sym)\n",
+    "        else: lab.append('Entrez_ID:' + gene_id)\n",
+    "        if 'None' not in [defn, gene_type, chrom, map_loc]:\n",
+    "            desc_str = \"{} has locus group '{}' and is located on chromosome {} ({}).\"\n",
+    "            desc.append(desc_str.format(sym, gene_type, chrom, map_loc))\n",
+    "        else: desc.append(\"{} locus group '{}'.\".format(sym, gene_type))\n",
+    "        if s1 != 'None' and s2 != 'None': syn.append('|'.join(set([x for x in (s1 + s2).split('|') if x != 'None' or x != ''])))\n",
+    "        elif s1 != 'None': syn.append('|'.join(set([x for x in s1.split('|') if x != 'None' or x != ''])))\n",
+    "        elif s2 != 'None': syn.append('|'.join(set([x for x in s2.split('|') if x != 'None' or x != ''])))\n",
+    "        else: syn.append('None')\n",
     "\n",
-    "# make all variables string\n",
-    "gene_metadata_final = gene_metadata_final.astype(str)\n",
-    "\n",
-    "# dedup\n",
-    "gene_metadata_final.drop_duplicates(subset='ID', keep='first', inplace=True)\n",
+    "# combine into new data frame\n",
+    "metadata = pandas.DataFrame(list(zip(genes, lab, desc, syn)), columns=['ID', 'Label', 'Description', 'Synonym'])\n",
+    "metadata = metadata.astype(str)\n",
+    "metadata.drop_duplicates(subset='ID', keep='first', inplace=True)\n",
     "\n",
     "# convert df to dictionary\n",
-    "gene_metadata_final.set_index('ID', inplace=True)\n",
-    "gene_metadata_dict = gene_metadata_final.to_dict('index')\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
+    "metadata.set_index('ID', inplace=True)\n",
+    "gene_metadata_dict = metadata.to_dict('index')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**RNA Metadata Dictionary**"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "rna_gene_data = pandas.read_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt',\n",
-    "                                header=0,\n",
-    "                                delimiter='\\t',\n",
-    "                                low_memory=False)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preprocess Data_  \n",
-    "Normal data preprocess and filtering steps are performed in order to prepare the data for the next step, which converts it to a metadata dictionary."
+    "***\n",
+    "\n",
+    "#### RNA Metadata Dictionary  <a class=\"anchor\" id=\"rna-metadata\"></a>\n",
+    "\n",
+    "The nested dictionary of rna metadata is created by looping over the cleaned human [Ensembl](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#ensembl) gene, RNA, and protein identifier data set (`ensembl_identifier_data_cleaned.txt`). The `keys` of the dictionary are `Ensembl transcript identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -8626,10 +3956,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# load data\n",
+    "rna_gene_data = pandas.read_csv(processed_data_location + 'ensembl_identifier_data_cleaned.txt', header=0, delimiter='\\t', low_memory=False)\n",
+    "\n",
     "# remove rows without identifiers\n",
     "rna_gene_data = rna_gene_data.loc[rna_gene_data['transcript_stable_id'].apply(lambda x: x != 'None')]\n",
     "\n",
-    "# remove unneede columns\n",
+    "# remove unneeded columns\n",
     "rna_gene_data.drop(['ensembl_gene_id', 'symbol', 'protein_stable_id', 'uniprot_id', 'master_transcript_type',\n",
     "                    'entrez_id', 'ensembl_gene_type', 'master_gene_type', 'symbol'], axis=1, inplace=True)\n",
     "\n",
@@ -8637,15 +3970,7 @@
     "rna_gene_data.drop_duplicates(subset=['transcript_stable_id', 'transcript_name', 'ensembl_transcript_type'], keep='first', inplace=True)\n",
     "\n",
     "# replace NaN with 'None'\n",
-    "rna_gene_data.fillna('None', inplace=True)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Create RNA Metadata Dictionary_  \n",
-    "The nested dictionary of rna metadata is created by looping over the cleaned human [Ensembl](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#ensembl) gene, RNA, and protein identifier data set ([`ensembl_identifier_data_cleaned.txt`](https://www.dropbox.com/s/gcq8157cqcdz2d0/ensembl_identifier_data_cleaned.txt?dl=1)). The `keys` of the dictionary are `Ensembl transcript identifiers` and the `values` are dictionaries for each metadata type: `symbol`, `description`, and `name`."
+    "rna_gene_data.fillna('None', inplace=True)"
    ]
   },
   {
@@ -8655,60 +3980,38 @@
    "outputs": [],
    "source": [
     "# create metadata\n",
-    "rna, label, description, synonym = [], [], [], []\n",
-    "\n",
+    "rna, lab, desc, syn = [], [], [], []\n",
     "for idx, row in tqdm(rna_gene_data.iterrows(), total=rna_gene_data.shape[0]):\n",
-    "    # node\n",
-    "    rna.append(row['transcript_stable_id'])\n",
-    "    \n",
-    "    # label info\n",
-    "    if row['transcript_name'] != 'None':\n",
-    "        label.append(row['transcript_name'])\n",
+    "    rna_id, ent_type, nme = row['transcript_stable_id'], row['ensembl_transcript_type'], row['transcript_name']\n",
+    "    rna.append('https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=' + rna_id)\n",
+    "    if nme != 'None':\n",
+    "        lab.append(nme)\n",
     "    else:\n",
-    "        rna_type = 'Ensembl_Transcript_ID:' + row['transcript_stable_id']\n",
-    "    \n",
-    "    # rna type info\n",
-    "    rna_type = row['ensembl_transcript_type']\n",
-    "\n",
-    "    if rna_type != 'None':\n",
-    "        # description\n",
-    "        description.append(\"Transcript {desc} is classified as type '{typ}'.\".format(desc=row['transcript_name'], typ=rna_type))\n",
-    "    else:\n",
-    "        # description\n",
-    "        description.append('None')\n",
+    "        lab.append('Ensembl_Transcript_ID:' + rna_id)\n",
+    "        nme = 'Ensembl_Transcript_ID:' + rna_id\n",
+    "    if ent_type != 'None': desc.append(\"Transcript {} is classified as type '{}'.\".format(nme, ent_type))\n",
+    "    else: desc.append('None')\n",
+    "    syn.append('None')\n",
     "\n",
-    "    # synonym\n",
-    "    synonym.append('None')\n",
-    "    \n",
     "# combine into new data frame\n",
-    "rna_metadata_final = pandas.DataFrame(list(zip(rna, label, description, synonym)),\n",
-    "                                      columns =['ID', 'Label', 'Description', 'Synonym'])\n",
+    "metadata = pandas.DataFrame(list(zip(rna, lab, desc, syn)), columns=['ID', 'Label', 'Description', 'Synonym'])\n",
+    "metadata = metadata.astype(str)\n",
+    "metadata.drop_duplicates(subset='ID', keep='first', inplace=True)\n",
     "\n",
     "# convert df to dictionary\n",
-    "rna_metadata_final.set_index('ID', inplace=True)\n",
-    "rna_metadata_dict = rna_metadata_final.to_dict('index')\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
+    "metadata.set_index('ID', inplace=True)\n",
+    "rna_metadata_dict = metadata.to_dict('index')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Variant Metadata Dictionary**"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Download Data_  \n",
-    "Only run this code block if the `variant_summary.txt` has not already been downloaded."
+    "***\n",
+    "\n",
+    "#### Variant Metadata Dictionary <a class=\"anchor\" id=\"variant-metadata\"></a>  \n",
+    "\n",
+    "The nested dictionary of rna metadata is created by looping over the human [ClinVar Variant](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#clinvar) identifier data set (`variant_summary.txt`). The `keys` of the dictionary are `dbSNP identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -8717,36 +4020,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# download data\n",
     "url = 'ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz'\n",
-    "data_downloader(url, unprocessed_data_location)\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "var_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt',\n",
-    "                           header=0,\n",
-    "                           delimiter='\\t',\n",
-    "                           low_memory=False)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Preprocess Data_  \n",
-    "Normal data preprocess and filtering steps are performed in order to prepare the data for the next step, which converts it to a metadata dictionary."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
+    "if not os.path.exists(unprocessed_data_location + 'variant_summary.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "\n",
+    "# load data\n",
+    "var_data = pandas.read_csv(unprocessed_data_location + 'variant_summary.txt', header=0, delimiter='\\t', low_memory=False)\n",
+    "\n",
     "# remove rows without identifiers\n",
     "var_data = var_data.loc[var_data['Assembly'].apply(lambda x: x == 'GRCh38')]\n",
     "var_data = var_data.loc[var_data['RS# (dbSNP)'].apply(lambda x: x != -1)]\n",
@@ -8757,19 +4038,12 @@
     "                         'Assembly', 'AlternateAllele','Cytogenetic', 'ReviewStatus', 'LastEvaluated']] \n",
     "\n",
     "# replace NaN with 'None'\n",
+    "var_metadata.replace('na', 'None', inplace=True)\n",
     "var_metadata.fillna('None', inplace=True)\n",
     "\n",
     "# remove duplicate dbSNP ids by choosing the most recent reviewed variant\n",
     "var_metadata.sort_values('LastEvaluated', ascending=False, inplace=True)\n",
-    "var_metadata.drop_duplicates(subset='RS# (dbSNP)', keep='first', inplace=True)\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "_Create Variant Metadata Dictionary_  \n",
-    "The nested dictionary of rna metadata is created by looping over the human [ClinVar Variant](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#clinvar) identifier data set ([`variant_summary.txt`](https://www.dropbox.com/s/nqsgf92jhu7690e/variant_summary.txt?dl=1)). The `keys` of the dictionary are `dbSNP identifiers` and the `values` are dictionaries for each metadata type: `symbol`, `description`, and `name`."
+    "var_metadata.drop_duplicates(subset='RS# (dbSNP)', keep='first', inplace=True)"
    ]
   },
   {
@@ -8779,54 +4053,40 @@
    "outputs": [],
    "source": [
     "# create metadata\n",
-    "variant, label, description = [], [], []\n",
-    "\n",
+    "variant, label, desc, syn = [], [], [], []\n",
     "for idx, row in tqdm(var_metadata.iterrows(), total=var_metadata.shape[0]):\n",
-    "    # node\n",
-    "    if row['RS# (dbSNP)'] != 'None':\n",
-    "        variant.append('rs' + str(row['RS# (dbSNP)']))\n",
+    "    var_id, lab = row['RS# (dbSNP)'], row['Name']\n",
+    "    if var_id != 'None':\n",
+    "        variant.append('https://www.ncbi.nlm.nih.gov/snp/rs' + str(var_id))\n",
+    "        if lab != 'None': label.append(lab)\n",
+    "        else: label.append('dbSNP_ID:rs' + str(var_id))\n",
+    "        sent = \"This variant is a {} {} located on chromosome {} ({}, start:{}/stop:{} positions, \" +\\\n",
+    "               \"cytogenetic location:{}) and has clinical significance '{}'. \" +\\\n",
+    "               \"This entry is for the {} and was last reviewed on {} with review status '{}'.\"\n",
+    "        desc.append(sent.format(row['Origin'].replace(';', '/'), row['Type'].replace(';', '/'), row['Chromosome'], row['ChromosomeAccession'],\n",
+    "                                row['Start'], row['Stop'], row['Cytogenetic'], row['ClinicalSignificance'],\n",
+    "                                row['Assembly'], row['LastEvaluated'], row['ReviewStatus']).replace('None', 'UNKNOWN'))\n",
+    "        syn.append('None')\n",
     "    \n",
-    "    # label -- only want metadata if there is a label\n",
-    "    if row['Name'] != 'None':\n",
-    "        label.append(row['Name'])\n",
-    "    else:\n",
-    "        label.append('dbSNP_ID:rs' + str(row['RS# (dbSNP)']))\n",
-    "    \n",
-    "    # description\n",
-    "    sent = \"This variant is a {Origin} {Type} that results when a {ReferenceAllele} allele is changed to {AlternateAllele} on chromosome {Chromosome} ({ChromosomeAccession}, start:{Start}/stop:{Stop} positions, cytogenetic location:{Cytogenetic}) and has clinical significance '{ClinicalSignificance}'. This entry is for the {Assembly} and was last reviewed on {LastEvaluated} with review status '{ReviewStatus}'.\"\n",
-    "    description.append(sent.format(Origin=row['Origin'], Type=row['Type'], ReferenceAllele=row['ReferenceAllele'],\n",
-    "                                   AlternateAllele=row['AlternateAllele'], Chromosome=row['Chromosome'],\n",
-    "                                   ChromosomeAccession=row['ChromosomeAccession'], Start=row['Start'],\n",
-    "                                   Stop=row['Stop'], Cytogenetic=row['Cytogenetic'], ClinicalSignificance=row['ClinicalSignificance'],\n",
-    "                                   Assembly=row['Assembly'], LastEvaluated=row['LastEvaluated'], ReviewStatus=row['ReviewStatus']))\n",
-    "\n",
     "# combine into new data frame\n",
-    "var_metadata_final = pandas.DataFrame(list(zip(variant, label, description)), columns =['ID', 'Label', 'Description'])\n",
-    "\n",
-    "# drop duplicates\n",
+    "var_metadata_final = pandas.DataFrame(list(zip(variant, label, desc, syn)), columns =['ID', 'Label', 'Description', 'Synonym'])\n",
     "var_metadata_final.drop_duplicates(subset=None, keep='first', inplace=True)\n",
-    "\n",
-    "# make all variables string\n",
     "var_metadata_final = var_metadata_final.astype(str)\n",
     "\n",
     "# convert df to dictionary\n",
     "var_metadata_final.set_index('ID', inplace=True)\n",
-    "var_metadata_dict = var_metadata_final.to_dict('index') \n"
+    "var_metadata_dict = var_metadata_final.to_dict('index') "
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Create Master Metadata Dictionary**  \n",
-    "To make it easier to navigate the mapping of each instance node in an edge, a master dictionary is created and keyed by node type. This is most useful when both nodes in an edge are instances, but of different data types (e.g. `gene-rna`).\n"
+    "***\n",
+    "\n",
+    "#### Pathway Metadata Dictionary <a class=\"anchor\" id=\"pathway-metadata\"></a>  \n",
+    "\n",
+    "The nested dictionary of pathway metadata is created by looping over the human [Reactome Pathway Database](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#reactome-pathway-database) identifier data set (`ReactomePathways.txt`). The `keys` of the dictionary are `Reactome identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -8835,56 +4095,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "master_metadata_dictionary = {'gene': gene_metadata_dict,\n",
-    "                              'rna': rna_metadata_dict,\n",
-    "                              'variant': var_metadata_dict}\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Write Metadata Files  <a class=\"anchor\" id=\"write-metadata-files\"></a>   \n",
-    "using the `Master Metadata Dictionary` created in the prior step, all of the `edge-type` data is processed and the resulting data written out `.txt` file to the `./resource/node_data` repository.\n",
-    "\n",
-    "- [Genes](#gene-metadata)\n",
-    "- [RNA](#rna-metadata)\n",
-    "- [Pathways](#pathway-metadata)\n",
-    "- [Variants](#variant-metadata)\n",
-    "\n",
-    "***"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Genes <a class=\"anchor\" id=\"gene-metadata\"></a>\n",
-    "\n",
-    "**Data Source Wiki Pages:** [NCBI Gene](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources/#ncbi-gene) \n",
+    "# download data\n",
+    "url = 'https://reactome.org/download/current/ReactomePathways.txt'\n",
+    "if not os.path.exists(unprocessed_data_location + 'ReactomePathways.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
     "\n",
-    "**Output:**  \n",
-    "- chemical-gene ➞ [`chemical-gene_GENE_METADATA.txt`](https://www.dropbox.com/s/jc332b9vo8qc1rm/chemical-gene_GENE_METADATA.txt?dl=1) \n",
-    "- gene-disease ➞ [`gene-disease_GENE_METADATA.txt`](https://www.dropbox.com/s/1gk34aoj28ze6r8/gene-disease_GENE_METADATA.txt?dl=1) \n",
-    "- gene-gene ➞ [`gene-gene_GENE_METADATA.txt`](https://www.dropbox.com/s/vxlgc7iblh02frp/gene-gene_GENE_METADATA.txt?dl=1) \n",
-    "- gene-pathway ➞ [`gene-pathway_GENE_METADATA.txt`](https://www.dropbox.com/s/vh5l5kwwxww1twc/gene-pathway_GENE_METADATA.txt?dl=1) \n",
-    "- gene-phenotype ➞ [`gene-phenotype_GENE_METADATA.txt`](https://www.dropbox.com/s/t1e7l6xqu4c1q8s/gene-phenotype_GENE_METADATA.txt?dl=1) \n",
-    "- gene-protein ➞ [`gene-protein_GENE_METADATA.txt`](https://www.dropbox.com/s/4t9inhmxw0mluh2/gene-protein_GENE_METADATA.txt?dl=1) \n",
-    "- gene-rna ➞ [`gene-rna_GENE_METADATA.txt`](https://www.dropbox.com/s/eoii2ee0j6j64id/gene-rna_GENE_METADATA.txt?dl=1) "
+    "# load data\n",
+    "reactome_pathways = pandas.read_csv(unprocessed_data_location + 'ReactomePathways.txt', header=None, delimiter='\\t', low_memory=False)\n",
+    "reactome_pathways = reactome_pathways.loc[reactome_pathways[2].apply(lambda x: x == 'Homo sapiens')] "
    ]
   },
   {
@@ -8893,90 +4111,27 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "node_type = 'gene'\n",
-    "\n",
-    "for edge_type in tqdm(metadata_file_info[node_type]):\n",
-    "    print('\\nPROCESSING EDGE TYPE: {}'.format(edge_type))\n",
-    "\n",
-    "    # gather vars for processing data\n",
-    "    data = metadata_file_info[node_type][edge_type]['data']\n",
-    "    edge_data_type = metadata_file_info[node_type][edge_type]['inst_sbc_idx']\n",
-    "    if 'entity' in edge_data_type: inst_idx = edge_data_type.split('-').index('entity')\n",
+    "# get metadata\n",
+    "nodes = set(list(reactome_pathways[0]))\n",
+    "pathway_metadata_final = metadata_api_mapper(list(nodes))\n",
     "\n",
-    "    # get list of nodes to map and the dictionary to use\n",
-    "    # when nodes are of the same type (i.e. gene-gene)\n",
-    "    if (edge_type.split('-')[0] == edge_type.split('-')[1]):\n",
-    "        nodes = set([x for y in data for x in y])\n",
-    "        \n",
-    "        if edge_type.split('-')[0] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[0]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
-    "            \n",
-    "    # when nodes are both instances, but different types (i.e. gene-rna)\n",
-    "    elif edge_data_type.split('-')[0] == edge_data_type.split('-')[1]:\n",
-    "        data_res = []\n",
-    "\n",
-    "        for node in edge_type.split('-'):\n",
-    "            nodes = set([x[int(edge_type.split('-').index(node))] for x in data])\n",
-    "            \n",
-    "            if node in master_metadata_dictionary:\n",
-    "                metadata_dictionaries = master_metadata_dictionary[node]\n",
-    "                data_res.append(metadata_dictionary_mapper(nodes, metadata_dictionaries))\n",
-    "            else:\n",
-    "                data_res.append(metadata_api_mapper(list(nodes)))\n",
-    "    \n",
-    "        # combine data into single df\n",
-    "        results = pandas.concat(data_res, ignore_index=True)\n",
-    "                \n",
-    "    # when only one node is an instance\n",
-    "    else:\n",
-    "        nodes = set([x[int(inst_idx)] for x in data])\n",
-    "        \n",
-    "        if edge_type.split('-')[int(inst_idx)] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[int(inst_idx)]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
+    "# update dictionary\n",
+    "pathway_metadata_final['ID'] = pathway_metadata_final['ID'].map('https://reactome.org/content/detail/{}'.format)\n",
+    "pathway_metadata_final.set_index('ID', inplace=True)\n",
     "\n",
-    "    # write data\n",
-    "    results.to_csv(node_data_location + edge_type + '_' + node_type.upper() + '_METADATA.txt',\n",
-    "                   header=True,\n",
-    "                   sep='\\t',\n",
-    "                   index=False)\n",
-    "    "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***"
+    "# convert df to dictionary\n",
+    "pathway_metadata_dict = pathway_metadata_final.to_dict('index') "
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### RNA<a class=\"anchor\" id=\"rna-metadata\"></a>\n",
+    "***\n",
     "\n",
-    "**Data Source Wiki Pages:**  \n",
-    "- [Ensembl](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#clinvar)  \n",
-    "- [NCBI Gene](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources/#ncbi-gene) \n",
+    "#### Relations Metadata Dictionary <a class=\"anchor\" id=\"relations-metadata\"></a>  \n",
     "\n",
-    "**Output:**  \n",
-    "- chemical-rna ➞ [`chemical-rna_RNA_METADATA.txt`](https://www.dropbox.com/s/eu2ihol7xyxuhrg/chemical-rna_RNA_METADATA.txt?dl=1) \n",
-    "- rna-anatomy ➞ [`rna-anatomy_RNA_METADATA.txt`](https://www.dropbox.com/s/dsktusrpeit9cpn/rna-anatomy_RNA_METADATA.txt?dl=1) \n",
-    "- rna-cell ➞ [`rna-cell_RNA_METADATA.txt`](https://www.dropbox.com/s/nnqb7911hdhbw7q/rna-cell_RNA_METADATA.txt?dl=1) \n",
-    "- rna-protein ➞ [`rna-protein_RNA_METADATA.txt`](https://www.dropbox.com/s/gjq1hydldyds4rz/rna-protein_RNA_METADATA.txt?dl=1) "
+    "The nested dictionary of relation metadata is created by looping over the human [Relations Ontology](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#relations-ontology) identifier data set (`ro_with_imports.owl`). The `keys` of the dictionary are `Relations Ontology identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -8985,89 +4140,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "node_type = 'rna'\n",
-    "\n",
-    "for edge_type in tqdm(metadata_file_info[node_type]):\n",
-    "    print('\\nPROCESSING EDGE TYPE: {}'.format(edge_type))\n",
-    "\n",
-    "    # gather vars for processing data\n",
-    "    data = metadata_file_info[node_type][edge_type]['data']\n",
-    "    edge_data_type = metadata_file_info[node_type][edge_type]['inst_sbc_idx']\n",
-    "    if 'entity' in edge_data_type: inst_idx = edge_data_type.split('-').index('entity')\n",
-    "\n",
-    "    # get list of nodes to map and the dictionary to use\n",
-    "    # when nodes are of the same type (i.e. gene-gene)\n",
-    "    if (edge_type.split('-')[0] == edge_type.split('-')[1]):\n",
-    "        nodes = set([x for y in data for x in y])\n",
-    "        \n",
-    "        if edge_type.split('-')[0] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[0]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
-    "            \n",
-    "    # when nodes are both instances, but different types (i.e. gene-rna)\n",
-    "    elif edge_data_type.split('-')[0] == edge_data_type.split('-')[1]:\n",
-    "        data_res = []\n",
-    "\n",
-    "        for node in edge_type.split('-'):\n",
-    "            nodes = set([x[int(edge_type.split('-').index(node))] for x in data])\n",
-    "            \n",
-    "            if node in master_metadata_dictionary:\n",
-    "                metadata_dictionaries = master_metadata_dictionary[node]\n",
-    "                data_res.append(metadata_dictionary_mapper(nodes, metadata_dictionaries))\n",
-    "            else:\n",
-    "                data_res.append(metadata_api_mapper(list(nodes)))\n",
-    "    \n",
-    "        # combine data into single df\n",
-    "        results = pandas.concat(data_res, ignore_index=True)\n",
-    "                \n",
-    "    # when only one node is an instance\n",
-    "    else:\n",
-    "        nodes = set([x[int(inst_idx)] for x in data])\n",
-    "        \n",
-    "        if edge_type.split('-')[int(inst_idx)] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[int(inst_idx)]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
-    "\n",
-    "    # write data\n",
-    "    results.to_csv(node_data_location + edge_type + '_' + node_type.upper() + '_METADATA.txt',\n",
-    "                   header=True,\n",
-    "                   sep='\\t',\n",
-    "                   index=False)\n",
-    "    "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Pathways<a class=\"anchor\" id=\"pathway-metadata\"></a>\n",
-    "\n",
-    "**Data Source Wiki Page:** [Reactome](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#reactome-pathway-database)  \n",
-    "\n",
-    "**Output:**    \n",
-    "- chemical-pathway ➞ [`chemical-pathway_PATHWAY_METADATA.txt`](https://www.dropbox.com/s/drs7av9ie5a6omr/chemical-pathway_PATHWAY_METADATA.txt?dl=1)\n",
-    "- gobp-pathway ➞ [`gobp-pathway_PATHWAY_METADATA.txt`](https://www.dropbox.com/s/wkfd7qhlkndzjay/gobp-pathway_PATHWAY_METADATA.txt?dl=1)\n",
-    "- pathway-gocc ➞ [`pathway-gocc_PATHWAY_METADATA.txt`](https://www.dropbox.com/s/tknn5uyobls9f4n/pathway-gocc_PATHWAY_METADATA.txt?dl=1)\n",
-    "- pathway-gomf ➞ [`pathway-gomf_PATHWAY_METADATA.txt`](https://www.dropbox.com/s/zr6arq66qmz08ws/pathway-gomf_PATHWAY_METADATA.txt?dl=1)\n",
-    "- protein-pathway ➞ [`protein-pathway_PATHWAY_METADATA.txt`](https://www.dropbox.com/s/cuvgwh2r8qtf08j/protein-pathway_PATHWAY_METADATA.txt?dl=1)"
+    "# download ontology\n",
+    "if not os.path.exists(unprocessed_data_location + 'ro_with_imports.owl'):\n",
+    "    command = \"./pkt_kg/libs/owltools {} --merge-import-closure -o {}\"\n",
+    "    os.system(command.format('http://purl.obolibrary.org/obo/ro.owl',\n",
+    "                             unprocessed_data_location + 'ro_with_imports.owl'))\n",
+    "# load graph\n",
+    "ro_graph = Graph().parse(unprocessed_data_location + 'ro_with_imports.owl')\n",
+    "print('There are {} edges in the ontology (date:{})'.format(len(ro_graph), datetime.datetime.now().strftime('%m/%d/%Y')))"
    ]
   },
   {
@@ -9076,87 +4156,41 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "node_type = 'pathway'\n",
-    "\n",
-    "for edge_type in tqdm(metadata_file_info[node_type]):\n",
-    "    print('\\nPROCESSING EDGE TYPE: {}'.format(edge_type))\n",
-    "\n",
-    "    # gather vars for processing data\n",
-    "    data = metadata_file_info[node_type][edge_type]['data']\n",
-    "    edge_data_type = metadata_file_info[node_type][edge_type]['inst_sbc_idx']\n",
-    "    if 'entity' in edge_data_type: inst_idx = edge_data_type.split('-').index('entity')\n",
-    "\n",
-    "    # get list of nodes to map and the dictionary to use\n",
-    "    # when nodes are of the same type (i.e. gene-gene)\n",
-    "    if (edge_type.split('-')[0] == edge_type.split('-')[1]):\n",
-    "        nodes = set([x for y in data for x in y])\n",
-    "        \n",
-    "        if edge_type.split('-')[0] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[0]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
-    "            \n",
-    "    # when nodes are both instances, but different types (i.e. gene-rna)\n",
-    "    elif edge_data_type.split('-')[0] == edge_data_type.split('-')[1]:\n",
-    "        data_res = []\n",
-    "\n",
-    "        for node in edge_type.split('-'):\n",
-    "            nodes = set([x[int(edge_type.split('-').index(node))] for x in data])\n",
-    "            \n",
-    "            if node in master_metadata_dictionary:\n",
-    "                metadata_dictionaries = master_metadata_dictionary[node]\n",
-    "                data_res.append(metadata_dictionary_mapper(nodes, metadata_dictionaries))\n",
-    "            else:\n",
-    "                data_res.append(metadata_api_mapper(list(nodes)))\n",
+    "# get metadata\n",
+    "relation_metadata_dict, obo = {}, Namespace('http://purl.obolibrary.org/obo/')\n",
+    "\n",
+    "# get ontology information\n",
+    "cls = [x for x in gets_ontology_classes(ro_graph) if '/RO_' in str(x)] +\\\n",
+    "      [x for x in gets_object_properties(ro_graph) if '/RO_' in str(x)]\n",
+    "master_synonyms = [x for x in ro_graph if 'synonym' in str(x[1]).lower() and isinstance(x[0], URIRef)]\n",
+    "\n",
+    "for x in tqdm(cls):\n",
+    "    # labels\n",
+    "    cls_label = list(ro_graph.objects(x, RDFS.label))\n",
+    "    labels = str(cls_label[0]) if len(cls_label) > 0 else 'None'\n",
+    "    # synonyms\n",
+    "    cls_syn = [str(i[2]) for i in master_synonyms if x == i[0]]\n",
+    "    synonym = str(cls_syn[0]) if len(cls_syn) > 0 else 'None'\n",
+    "    # description\n",
+    "    cls_desc = list(ro_graph.objects(x, obo.IAO_0000115))\n",
+    "    desc = '|'.join([str(cls_desc[0])]) if len(cls_desc) > 0 else 'None'\n",
     "    \n",
-    "        # combine data into single df\n",
-    "        results = pandas.concat(data_res, ignore_index=True)\n",
-    "                \n",
-    "    # when only one node is an instance\n",
-    "    else:\n",
-    "        nodes = set([x[int(inst_idx)] for x in data])\n",
-    "        \n",
-    "        if edge_type.split('-')[int(inst_idx)] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[int(inst_idx)]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
-    "\n",
-    "    # write data\n",
-    "    results.to_csv(node_data_location + edge_type + '_' + node_type.upper() + '_METADATA.txt',\n",
-    "                   header=True,\n",
-    "                   sep='\\t',\n",
-    "                   index=False)\n",
-    "    "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***"
+    "    relation_metadata_dict[str(x)] = {\n",
+    "        'Label': labels,\n",
+    "        'Description': desc,\n",
+    "        'Synonym': synonym\n",
+    "    }"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Variants<a class=\"anchor\" id=\"variant-metadata\"></a>\n",
+    "***\n",
     "\n",
-    "**Data Source Wiki Page:** [ClinVar](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#clinvar)  \n",
+    "**Create Master Metadata Dictionary** \n",
     "\n",
-    "**Output:**  \n",
-    "- variant-disease ➞ [`variant-disease_VARIANT_METADATA.txt`](https://www.dropbox.com/s/eiyh1bc7lttdmut/variant-disease_VARIANT_METADATA.txt?dl=1)  \n",
-    "- variant-gene ➞ [`variant-gene_VARIANT_METADATA.txt`](https://www.dropbox.com/s/luafq6fa07eljn9/variant-gene_VARIANT_METADATA.txt?dl=1)  \n",
-    "- variant-phenotype ➞ [`variant-phenotype_VARIANT_METADATA.txt`](https://www.dropbox.com/s/fhphtuy6ecq1bkx/variant-phenotype_VARIANT_METADATA.txt?dl=1)  "
+    "To make it easier to navigate the mapping of each instance node in an edge, a master dictionary is created and keyed by node type. This is most useful when both nodes in an edge are instances, but of different data types (e.g. `gene-rna`).\n"
    ]
   },
   {
@@ -9165,59 +4199,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "node_type = 'variant'\n",
-    "\n",
-    "for edge_type in tqdm(metadata_file_info[node_type]):\n",
-    "    print('\\nPROCESSING EDGE TYPE: {}'.format(edge_type))\n",
-    "\n",
-    "    # gather vars for processing data\n",
-    "    data = metadata_file_info[node_type][edge_type]['data']\n",
-    "    edge_data_type = metadata_file_info[node_type][edge_type]['inst_sbc_idx']\n",
-    "    if 'entity' in edge_data_type: inst_idx = edge_data_type.split('-').index('entity')\n",
-    "\n",
-    "    # get list of nodes to map and the dictionary to use\n",
-    "    # when nodes are of the same type (i.e. gene-gene)\n",
-    "    if (edge_type.split('-')[0] == edge_type.split('-')[1]):\n",
-    "        nodes = set([x for y in data for x in y])\n",
-    "        \n",
-    "        if edge_type.split('-')[0] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[0]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = hmetadata_api_mapper(list(nodes))\n",
-    "            \n",
-    "    # when nodes are both instances, but different types (i.e. gene-rna)\n",
-    "    elif edge_data_type.split('-')[0] == edge_data_type.split('-')[1]:\n",
-    "        data_res = []\n",
-    "\n",
-    "        for node in edge_type.split('-'):\n",
-    "            nodes = set([x[int(edge_type.split('-').index(node))] for x in data])\n",
-    "            \n",
-    "            if node in master_metadata_dictionary:\n",
-    "                metadata_dictionaries = master_metadata_dictionary[node]\n",
-    "                data_res.append(metadata_dictionary_mapper(nodes, metadata_dictionaries))\n",
-    "            else:\n",
-    "                data_res.append(metadata_api_mapper(list(nodes)))\n",
-    "    \n",
-    "        # combine data into single df\n",
-    "        results = pandas.concat(data_res, ignore_index=True)\n",
-    "                \n",
-    "    # when only one node is an instance\n",
-    "    else:\n",
-    "        nodes = set([x[int(inst_idx)] for x in data])\n",
-    "        \n",
-    "        if edge_type.split('-')[int(inst_idx)] in master_metadata_dictionary.keys():\n",
-    "            metadata_dictionaries = master_metadata_dictionary[edge_type.split('-')[int(inst_idx)]]\n",
-    "            results = metadata_dictionary_mapper(nodes, metadata_dictionaries)\n",
-    "        else:\n",
-    "            results = metadata_api_mapper(list(nodes))\n",
+    "# combine all metadata dictionaries\n",
+    "master_metadata_dictionary = {**gene_metadata_dict,\n",
+    "                              **rna_metadata_dict,\n",
+    "                              **var_metadata_dict,\n",
+    "                              **pathway_metadata_dict,\n",
+    "                              **relation_metadata_dict}\n",
     "\n",
-    "    # write data\n",
-    "    results.to_csv(node_data_location + edge_type + '_' + node_type.upper() + '_METADATA.txt',\n",
-    "                   header=True,\n",
-    "                   sep='\\t',\n",
-    "                   index=False)\n",
-    "    "
+    "# save dictionary locally\n",
+    "pickle.dump(master_metadata_dictionary, open(node_data_location + 'node_metadata_dict.pkl', 'wb'), protocol=4)"
    ]
   },
   {
@@ -9241,13 +4231,6 @@
     "}\n",
     "```"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
diff --git a/Dockerfile b/Dockerfile
index 1e4fa8403..e7e66ef78 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,7 +18,9 @@ RUN wget -O- https://apt.corretto.aws/corretto.key | apt-key add - && \
 ############################################
 ## PHEKNOWLATOR (PKT_KG) PROJECT SETTINGS ##
 # create needed project directories
-RUN mkdir /PheKnowLator
+WORKDIR /PheKnowLator
+# I THINK WE NEED LINE 23 -- which I had to delete to get it to run past this point
+#RUN mkdir /PheKnowLator
 RUN mkdir /PheKnowLator/resources
 RUN mkdir /PheKnowLator/resources/edge_data
 RUN mkdir /PheKnowLator/resources/knowledge_graphs
@@ -35,6 +37,7 @@ COPY resources /PheKnowLator/resources
 
 # install needed python libraries
 RUN pip install --upgrade pip setuptools
+# BILL -- CAN WE REMOVE LINE  41 if we had to add line 21 to get the container to run
 WORKDIR /PheKnowLator
 RUN pip install .
 
diff --git a/Ontology_Cleaning.ipynb b/Ontology_Cleaning.ipynb
index 7bafb038d..0301f6cb0 100644
--- a/Ontology_Cleaning.ipynb
+++ b/Ontology_Cleaning.ipynb
@@ -19,48 +19,21 @@
     "This notebook serves as a script to help prepare ontologies prior to be ingested into the knowledge graph build algorithm. This script performs the following steps:  \n",
     "1. [Clean Ontologies](#clean-ontologies)  \n",
     "2. [Merge Ontologies](#merge-ontologies)  \n",
-    "3. [Normalize Classes](#normalize-classes)\n",
     "\n",
     "## Assumptions and Dependencies  \n",
     "  \n",
     "**Assumptions:**   \n",
-    "- Knowledge Graph Build Steps 1-2 (i.e. data downloading and master edge list creation) have already been performed  \n",
-    "- Directory of Imported Ontologies ➞ `./resources/ontologies`    \n",
-    "- Processed data write location ➞ `./resources/ontologies`  \n",
+    "- Directory of Imported Ontologies has been populated ➞ `./resources/ontologies`    \n",
+    "- Processed data write location established ➞ `./resources/ontologies`  \n",
     "\n",
     "**Dependencies:**   \n",
-    "- This notebook utilizes several helper functions, which are stored in the [`kg_utils.py`](https://github.com/callahantiff/PheKnowLator/blob/master/pkt_kg/utils/kg_utils.py) script. Hyperlinks to all downloaded and generated data sources are provided on the [Data Sources](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources) Wiki page as well as within each source subsection of this notebook. All generated data is freely available for download from DropBox. \n",
-    "- [`OWLTools`](https://github.com/owlcollab/owltools)\n",
-    " "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>\n",
-    "\n",
-    "_____\n",
-    "\n",
-    "### PheKnowLator Build V2.0.0 Ontology Cleaning Summary  \n",
-    "**Date:** `12/01/2020` \n",
-    "***\n",
+    "- <u>Scripts</u>: This notebook utilizes several helper functions, which are stored in [utility scripts](https://github.com/callahantiff/PheKnowLator/blob/master/pkt_kg/utils) and the main code to perform the error checks in the  [`builds/temp/ontology_cleaning.py`](https://github.com/callahantiff/PheKnowLator/blob/master/builds/ontology_cleaning.py) script.\n",
+    "- <u>Software</u>:[`OWLTools`](https://github.com/owlcollab/owltools)  \n",
+    "- <u>Data</u>: [`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/Merged_gene_rna_protein_identifiers.pkl), which is automatically downloaded to the `./resources/ontologies` directory     \n",
     "\n",
-    "The table below is meant to provide a high-level overview of the modifications that we applied to each individual ontology as well as to the merged ontology file for the latest PheKnowLator build.\n",
+    "<br>\n",
     "\n",
-    "Ontology | Value Errors | Punning Errors | Class Identifier Errors | Obsolete/Deprecated Classes | Normalization Errors  \n",
-    ":---:    | :---:        | :---:          | :---:                   | :---:                       | :---:  \n",
-    "ChEBI    | 0            | 0              | 0                       | 14/18498                    | X    \n",
-    "CLO      | 1            | 8              | 0                       | 14/2                        | X    \n",
-    "MONDO    | 0            | 0              | 0                       | 1945/2196                   | X    \n",
-    "GO       | 0            | 0              | 0                       | 3121/6302                   | X    \n",
-    "HP       | 0            | 0              | 0                       | 297/348                     | X  \n",
-    "PW       | 0            | 0              | 0                       | 6/42                        | X    \n",
-    "PRO - Human | 0         | 0              | 0                       | 0/0                         | X  \n",
-    "SO       | 0            | 0              | 0                       | 0/0                         | X    \n",
-    "UBERON   | 0            | 0              | 0                       | 1062/1564                   | X   \n",
-    "VO       | 0            | 0              | 2                       | 10/0                        | X    \n",
-    "RO       | 0            | 0              | 0                       | 0/0                         | X  "
+    "Details on the data utilized in this script can be found on the [Data Sources](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources) Wiki. Data can be downloaded from [this](https://console.cloud.google.com/storage/browser/pheknowlator/release_v2.0.0?project=pheknowlator) dedicated Google Cloud Storage Bucket. Please note that all build data are freely available and organized by release and build date. "
    ]
   },
   {
@@ -76,428 +49,86 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# import needed libraries\n",
-    "import glob\n",
-    "import pickle\n",
-    "\n",
-    "from owlready2 import *\n",
-    "from rdflib import Graph, Namespace, URIRef, BNode, Literal\n",
-    "from rdflib.namespace import OWL, RDF, RDFS \n",
-    "from rdflib.plugins.sparql import prepareQuery\n",
-    "\n",
-    "# import script containing helper functions\n",
-    "from pkt_kg.utils import * "
+    "# # uncomment and update to install any required modules\n",
+    "# import sys\n",
+    "# !{sys.executable} -m pip install <<MODULE NAME>>"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# set up environment variables\n",
-    "ontologies = ['chebi', 'clo', 'ext', 'go', 'hp', 'mondo', 'pro', 'pw', 'ro', 'so', 'vo']\n",
-    "write_location = 'resources/ontologies'\n",
-    "merged_ontology_file = '/PheKnowLator_MergedOntologies.owl'\n",
-    "ontology_repository = glob.glob('*/ontologies/*.owl')"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "***\n",
-    "## Clean Ontologies <a class=\"anchor\" id=\"clean-ontologies\"></a>\n",
-    "***\n",
-    "\n",
-    "**Purpose:** In this step, we read in the ontologies using the [`owlready2`](https://pypi.org/project/Owlready2/) library and use it to indicate the presence of errors in the ontology files. We use this tool because it has strict filters. Using this tool we performed the following checks to clean the ontologies:\n",
-    "\n",
-    "* [Value Errors](#value-error)  \n",
-    "* [Punning Errors](#punning-error)  \n",
-    "* [Class Identifier Check](#identifier-check)  \n",
-    "* [Obsolete/Deprecated Classes](#obsolete-classes)  \n",
-    "\n",
-    "***"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>\n",
-    "\n",
-    "### Value Errors <a class=\"anchor\" id=\"value-error\"></a>\n",
-    "***\n",
-    "\n",
-    "This check utilizes the [`owlready2`](https://pypi.org/project/Owlready2/) library to read in each of the ontologies. This library is strict and will catch a wide variety of value errors. \n",
-    "\n",
-    "#### Findings  \n",
-    "Of the 11 ontologies utilized in the PheKnowLator `v2.0` build, only 1 had a value error. The [Cell Line Ontology](http://www.clo-ontology.org/) yield the following error message:\n",
-    "\n",
-    "```python\n",
-    "ValueError: invalid literal for int() with base 10: '永生的乳腺衍生细胞系细胞'\n",
-    "...\n",
-    "OwlReadyOntologyParsingError: RDF/XML parsing error in file ./resources/knowledge_graphs/PheKnowLator_MergedOntologies.owl, line 2363344, column 99.\n",
-    "```\n",
+    "# import needed libraries\n",
+    "import datetime\n",
+    "import glob\n",
+    "import pickle\n",
     "\n",
-    "**Interpretation:** This tells us that we need to repair the triple containing the Literal '永生的乳腺衍生细胞系细胞' by removing it and redefining it as a `string`, rather than an `int` as it is currently defined as. \n",
+    "from rdflib import Graph\n",
+    "from tqdm import tqdm\n",
     "\n",
-    "**Solution:** Retype the edge correctly. This is currently noted as an issue in the [Cell Line Ontology's](http://www.clo-ontology.org/) GitHub repo ([issue #48](https://github.com/CLO-ontology/CLO/issues/48)). "
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Loading: resources/ontologies/chebi_lite_with_imports.owl\n",
-      "Loading: resources/ontologies/clo_with_imports.owl\n",
-      "Loading: resources/ontologies/ext_with_imports.owl\n",
-      "Loading: resources/ontologies/go_with_imports.owl\n",
-      "Loading: resources/ontologies/hp_with_imports.owl\n",
-      "Loading: resources/ontologies/human_pro_closed.owl\n",
-      "Loading: resources/ontologies/mondo_with_imports.owl\n",
-      "Loading: resources/ontologies/pw_with_imports.owl\n",
-      "Loading: resources/ontologies/ro_with_imports.owl\n",
-      "Loading: resources/ontologies/so_with_imports.owl\n",
-      "Loading: resources/ontologies/vo_with_imports.owl\n"
-     ]
-    }
-   ],
-   "source": [
-    "for ont in ontology_repository:\n",
-    "    print('Loading: {}'.format(ont))\n",
-    "    load_onto = get_ontology(ont).load()\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Value Error Repairs**  \n",
-    "Code to fix the Cell Ontology *Value Error* is provided below."
+    "# import script containing helper functions\n",
+    "from pkt_kg.utils import * \n",
+    "from builds.ontology_cleaning import *"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "# load the graph\n",
-    "graph = Graph().parse(write_location + '/clo_with_imports.owl')\n",
-    "\n",
-    "# fix the error\n",
-    "for edge in graph:\n",
-    "    if '永生的乳腺衍生细胞系细胞' in str(edge[0]) or '永生的乳腺衍生细胞系细胞' in str(edge[2]):\n",
-    "        \n",
-    "        # repair broken triple\n",
-    "        graph.add((edge[0], edge[1], Literal(str(edge[2]), datatype=URIRef('http://www.w3.org/2001/XMLSchema#string'))))\n",
-    "        graph.remove(edge)\n",
-    "        break\n",
+    "# set up environment variables\n",
+    "write_location = 'resources/ontologies'\n",
+    "knowledge_graphs_location = 'resources/knowledge_graphs'\n",
+    "processed_data_location = 'resources/processed_data/'\n",
     "\n",
-    "# save cleaned up ontology\n",
-    "graph.serialize(destination=write_location + '/clo_with_imports.owl', format='xml')\n",
-    "ontology_file_formatter(write_location, '/clo_with_imports.owl', './pkt_kg/libs/owltools')"
+    "# set global namespaces\n",
+    "schema = Namespace('http://www.w3.org/2001/XMLSchema#')\n",
+    "obo = Namespace('http://purl.obolibrary.org/obo/')\n",
+    "oboinowl = Namespace('http://www.geneontology.org/formats/oboInOwl#')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# try reading in the cleaned ontology again -- there should be no errors this time!\n",
-    "clo_onto = get_ontology(write_location + '/clo_with_imports.owl').load()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>\n",
-    "\n",
-    "### Punning Errors <a class=\"anchor\" id=\"punning-error\"></a>\n",
-    "***\n",
+    "# functions needed for processing ontologies\n",
+    "def logically_verifies_cleaned_ontologies(graph, temp_dir, file_location, owltools_location):\n",
+    "    \"\"\"Logically verifies an ontology by running the ELK deductive logic reasoner. Before running the reasoner\n",
+    "    the instantiated RDFLib object is saved locally.\n",
     "\n",
-    "[Punning](https://www.w3.org/2007/OWL/wiki/Punning) or redeclaration errors occur for a few different reasons, but the primary or most prevalent cause observed in the ontologies used in `PheKnowLator` is due to an `owl:ObjectProperty` being incorrectly redeclared as an `owl:AnnotationProperty` or an `owl:Class` also being defined as an `OWL:ObjectProeprty`. To detect these types of errors we currently rely on [OWLTools](https://github.com/owlcollab/owltools) and the [`owlready2`](https://pypi.org/project/Owlready2/) Python library. The easiest way to check for these types of errors is to merge the ontologies together, during which, an error message will be generated should any errors be found.\n",
-    "\n",
-    "#### Findings  \n",
-    "The [Cell Line Ontology](http://www.clo-ontology.org/) had 7 object properties that were illegally redeclared and triggered punning errors. More details regarding these errors are shown below. \n",
-    "\n",
-    "```bash\n",
-    "2020-12-03 20:57:15,616 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002091 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002091>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002091>))]\n",
-    "2020-12-03 20:57:15,619 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000062 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000062>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000062>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000063 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000063>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000063>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002222 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002222>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002222>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0000087 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0000087>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0000087>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002161 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002161>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002161>))]\n",
-    "```\n",
-    "\n",
-    "From this message, we can see that we need to remove the following `owl:ObjectProperty` redeclared to `owl:AnnotationProperty`: `RO_0002091`, `BFO_0000062`, `BFO_0000063`, `RO_0002222`, `RO_0000087`, `RO_0002161`. There were also 2 classes (i.e. `CLO_0054407` and `CLO_0054409`) defined as being a `owl:Class` and an `owl:ObjectProperty`.\n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Solution:** Consistent with the solution described [here](https://github.com/oborel/obo-relations/issues/130), we removed all `owl:AnnotationProperty` declarations. For the class defined as a class and object property, we removed the class annotation. This is currently noted as an issue in the Cell Line Ontology's GitHub repo [issue #43](https://github.com/CLO-ontology/CLO/issues/43))."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/chebi_lite_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/clo_with_imports.owl\n",
-      "Punning Error: http://purl.obolibrary.org/obo/CLO_0054407 defined as an owl:Class and owl:ObjectProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/BFO_0000062 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/CLO_0054409 defined as an owl:Class and owl:ObjectProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/BFO_0000063 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/RO_0002161 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/RO_0002222 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/RO_0000087 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "Punning Error: http://purl.obolibrary.org/obo/RO_0002091 defined as an owl:ObjectProperty and owl:AnnotationProperty\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/ext_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/go_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/hp_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/human_pro_closed.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/mondo_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/pw_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/ro_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/so_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "\n",
-      "\n",
-      "Processing Ontology: resources/ontologies/vo_with_imports.owl\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n"
-     ]
-    }
-   ],
-   "source": [
-    "for ont in ontology_repository:\n",
-    "    print('\\n\\n\\nProcessing Ontology: {}'.format(ont))\n",
-    "    graph, bad_classes = Graph().parse(ont), set()\n",
-    "    \n",
-    "    for s, p, o in graph:\n",
-    "        triples = list(graph.triples((s, None, None)))\n",
-    "        \n",
-    "        # check for objects defined as classes and object properties\n",
-    "        class_prop, obj_prop = (s, RDF.type, OWL.Class), (s, RDF.type, OWL.ObjectProperty)\n",
-    "        if (class_prop in triples and obj_prop in triples) and str(s) not in bad_classes:\n",
-    "            bad_classes.add(str(s))\n",
-    "            print('Punning Error: {} defined as an owl:Class and owl:ObjectProperty'.format(str(s)))\n",
-    "            graph.remove(class_prop)\n",
+    "    Args:\n",
+    "        graph: An RDFLib Graph object containing data.\n",
+    "        temp_dir: A string specifying where where to read from and write to.\n",
+    "        file_location: The name of the file to read and write to in the temp_dir directory.\n",
+    "        owltools_location: A string specifying the location of OWLTOOLs (included in pkt_kg no need to download).\n",
     "    \n",
-    "        # check for objects defined as object properties and annotation properties\n",
-    "        if o == URIRef('http://www.w3.org/2002/07/owl#ObjectProperty'):\n",
-    "            obj_prop, annot_prop = (s, RDF.type, OWL.ObjectProperty), (s, RDF.type, OWL.AnnotationProperty)\n",
-    "            if obj_prop in triples and annot_prop in triples:\n",
-    "                print('Punning Error: {} defined as an owl:ObjectProperty and owl:AnnotationProperty'.format(str(s)))\n",
-    "                graph.remove(annot_prop)\n",
-    "    \n",
-    "    # save cleaned up ontology\n",
-    "    graph.serialize(destination=ont, format='xml') \n",
-    "    ontology_file_formatter(write_location, '/' + ont.split('/')[-1], './pkt_kg/libs/owltools')"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>\n",
+    "    Returns:\n",
+    "        None.\n",
+    "    \"\"\"\n",
     "\n",
-    "### Class Identifier Check  <a class=\"anchor\" id=\"identifier-check\"></a>\n",
-    "***\n",
+    "    print('Logically Verifying Ontology')\n",
     "\n",
-    "Check class identifiers to ensure consistency in identifier prefixes. For example, we want to identifiers that are incorrectly formatted like occurrences of `PRO_XXXXXXX` which should be `PR_XXXXXXX`.\n",
-    "\n",
-    "#### Findings  \n",
-    "Running this check revealed mislabeling of `18` [pROtein Ontology](https://proconsortium.org/) identifiers in the [Vaccine Ontology](http://www.violinet.org/vaccineontology/) (see [this](https://github.com/vaccineontology/VO/issues/4) GitHub issue).\n",
-    "\n",
-    "\n",
-    "**Solution:** Reformat the incorrectly formatted class identifiers.\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 9,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "\n",
-      "Processing Ontology: chebi\n",
-      "Unique Identifier Types: CHEBI\n",
-      "\n",
-      "\n",
-      "Processing Ontology: clo\n",
-      "Unique Identifier Types: BFO, CARO, CHEBI, CL, CLO, CP, DOID, GO, HGNC, IAO, NCBITaxon, NCIT, OBI, OGG, PATO, PR, SO, UBERON, VO\n",
-      "\n",
-      "\n",
-      "Processing Ontology: ext\n",
-      "Unique Identifier Types: BFO, BSPOTEMP, CARO, CHEBI, CL, CP, D96882F1-8709-49AB-BCA9-772A67EA6C33, EHDAA2, EMAPA, ENVO, FBbt, FMA, GO, NBO, NCBITaxon, PATO, PR, RO, RnorDv, UBERON, UBERON#, UBERONTEMP, ZFA\n",
-      "\n",
-      "\n",
-      "Processing Ontology: go\n",
-      "Unique Identifier Types: GO\n",
-      "\n",
-      "\n",
-      "Processing Ontology: hp\n",
-      "Unique Identifier Types: BFO, CARO, CHEBI, CL, CP, ENVO, GO, HP, HsapDv, MOD, MPATH, NBO, NCBITaxon, OBI, PATO, PR, RO, SO, UBERON\n",
-      "\n",
-      "\n",
-      "Processing Ontology: mondo\n",
-      "Unique Identifier Types: BFO, CARO, CHEBI, CL, CP, ECTO, ENVO, ExO, FOODON, GO, HP, IAO, MF, MFOEM, MFOMD, MONDO, NBO, NCBITaxon, NCIT, OBA, OBI, OGMS, PATO, PCO, PO, PR, RO, SO, UBERON, UMLS, UPHENO\n",
-      "\n",
-      "\n",
-      "Processing Ontology: pro\n",
-      "Unique Identifier Types: BFO, CHEBI, CL, GO, MOD, NCBITaxon, OBI, PR, SO\n",
-      "\n",
-      "\n",
-      "Processing Ontology: pw\n",
-      "Unique Identifier Types: OMIM, PW\n",
-      "\n",
-      "\n",
-      "Processing Ontology: ro\n",
-      "Unique Identifier Types: BFO, CHEBI, CL, GO, MOD, NCBITaxon, OBI, PR, SO\n",
-      "\n",
-      "\n",
-      "Processing Ontology: so\n",
-      "Unique Identifier Types: CHEBI\n",
-      "\n",
-      "\n",
-      "Processing Ontology: vo\n",
-      "Unique Identifier Types: BFO, CHEBI, DOID, FMA, GO, IAO, IDO, NCBITaxon, OAE, OBI, OGG, OGMS, PATO, PRO, UBERON, UO, VO\n"
-     ]
-    }
-   ],
-   "source": [
-    "for ont in ontologies:\n",
-    "    print('\\n\\nProcessing Ontology: {}'.format(ont))\n",
-    "    ont_data = [x for x in ontology_repository if ont.lower() in x][0]\n",
-    "    graph = Graph().parse(ont_data)\n",
-    "    \n",
-    "    # get classes\n",
-    "    kg_classes = set([x for x in graph.subjects(RDF.type, OWL.Class)])\n",
+    "    # save graph in order to run reasoner\n",
+    "    filename = temp_dir + '/' + file_location\n",
+    "    graph.serialize(destination=filename, format='xml')\n",
     "    \n",
-    "    # convert results to list of classes and only keep hgnc identifiers\n",
-    "    class_list = [res for res in kg_classes if isinstance(res, URIRef) and 'obo/' in str(res)]\n",
-    "\n",
-    "    # print unique identifier types for all classes in each ontology\n",
-    "    print('Unique Identifier Types: {}'.format(', '.join(sorted(set([x.split('/')[-1].split('_')[0] for x in class_list])))))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 15,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The following classes were updated:\n",
-      "http://purl.obolibrary.org/obo/PRO_000000001\n",
-      "http://purl.obolibrary.org/obo/PRO_000015399\n"
-     ]
-    }
-   ],
-   "source": [
-    "# replace badly formatted identifiers with the correct ones\n",
-    "graph = Graph().parse('resources/ontologies/vo_with_imports.owl')\n",
-    "bad_classes = set()\n",
+    "    # run reasoner\n",
+    "    command = \"./pkt_kg/libs/owltools {} --reasoner {} --run-reasoner --assert-implied -o {}\"\n",
+    "    return_code = os.system(command.format(filename, 'elk', filename))\n",
+    "    if return_code == 0:\n",
+    "        ontology_file_formatter(temp_dir, '/' + file_location, owltools_location)\n",
+    "    else: raise ValueError('Reasoner Finished with Errors.')\n",
     "\n",
-    "for edge in graph:\n",
-    "    if 'http://purl.obolibrary.org/obo/PRO_' in str(edge[0]):\n",
-    "        updated_subj = str(edge[0]).replace('http://purl.obolibrary.org/obo/PRO_', 'http://purl.obolibrary.org/obo/PR')\n",
-    "        graph.add((URIRef(updated_subj), edge[1], edge[2]))\n",
-    "        graph.remove(edge)\n",
-    "        bad_classes.add(str(edge[0]))\n",
-    "    if 'http://purl.obolibrary.org/obo/PRO_' in str(edge[2]):\n",
-    "        updated_obj = str(edge[0]).replace('http://purl.obolibrary.org/obo/PRO_', 'http://purl.obolibrary.org/obo/PR')\n",
-    "        graph.add((edge[0], edge[1], URIRef(updated_obj)))\n",
-    "        graph.remove(edge)\n",
-    "        bad_classes.add(str(edge[2]))\n",
-    "\n",
-    "print('The following classes were updated:\\n{}'.format('\\n'.join(bad_classes)))\n",
-    "\n",
-    "# save cleaned up ontology\n",
-    "graph.serialize(destination='resources/ontologies/vo_with_imports.owl', format='xml') \n",
-    "ontology_file_formatter(write_location, '/' + 'vo_with_imports.owl', './pkt_kg/libs/owltools')"
+    "    return None"
    ]
   },
   {
@@ -506,253 +137,57 @@
    "source": [
     "<br>\n",
     "\n",
-    "### Remove Obsolete and/or Deprecated Classes    <a class=\"anchor\" id=\"obsolete-classes\"></a>\n",
+    "***\n",
+    "## CLEAN ONTOLOGIES <a class=\"anchor\" id=\"clean-ontologies\"></a>\n",
     "***\n",
     "\n",
-    "To make sure that the ontology only contains current information, all obsolete classes and any triples that they participate in are removed from the ontologies. For build `V2.0`, we removed the following number of entities related to or containing a `deprecated` or `obsolete` ontology class:    \n",
-    "\n",
-    "Ontology | Obsolete | Deprecated\n",
-    ":--: | :--: | :--:\n",
-    "CheBI Lite | 14 | 18498   \n",
-    "CLO | 14 | 2    \n",
-    "MONDO | 1945 | 2196    \n",
-    "GO | 3121 | 6302    \n",
-    "HPO | 297 | 348   \n",
-    "PW | 6 | 42   \n",
-    "PRO - Human | 0 | 0   \n",
-    "SO | 0 | 0   \n",
-    "UBERON | 1062 | 1564    \n",
-    "VO | 10 | 0   \n",
-    "RO | 0 | 0   \n",
+    "The ontology cleaning step includes the following error checks, each of which are explained below: (1) Value Errors, (2) Identifier Errors, (3) Duplicate and Obsolete Entities, (4) Punning Errors, and (5) Entity Normalization Errors.\n",
     "\n",
+    "<br>\n",
     "\n",
-    "_NOTE._ In addition to running the code below, it may also be necessary to check for classes that are a sub-class of `oboInOwl:ObsoleteClass`, as well as any obsolete or deprecated annotations, individuals, or `owl:ObjectProperty`. Finally, we recommend verifying each ontology using an ontology debugger (i.e. by running a reasoner) to ensure that your changes and edits have not introduced unexpected errors.\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 16,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "Loading: chebi\n",
-      "Removed 14 obsolete classes and 18498 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: clo\n",
-      "Removed 14 obsolete classes and 2 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: ext\n",
-      "Removed 1062 obsolete classes and 1564 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "None\n",
-      "\n",
-      "Loading: go\n",
-      "Removed 3121 obsolete classes and 6302 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: hp\n",
-      "Removed 297 obsolete classes and 348 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: mondo\n",
-      "Removed 1945 obsolete classes and 2196 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "None\n",
-      "\n",
-      "Loading: pro\n",
-      "Removed 0 obsolete classes and 0 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: pw\n",
-      "Removed 6 obsolete classes and 42 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: ro\n",
-      "Removed 0 obsolete classes and 0 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: so\n",
-      "Removed 0 obsolete classes and 0 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n",
-      "\n",
-      "Loading: vo\n",
-      "Removed 10 obsolete classes and 0 deprecated classes\n",
-      "\n",
-      "\n",
-      "*** Applying OWL API Formatting to Knowledge Graph OWL File ***\n"
-     ]
-    }
-   ],
-   "source": [
-    "# remove triples containing deprecated classes\n",
-    "for ont in ontologies:    \n",
-    "    print('\\nLoading: {}'.format(ont))\n",
-    "    ont_switch = ont if ont != 'ext' else 'uberon'\n",
-    "    ont_prefix = 'http://purl.obolibrary.org/obo/' + ont_switch.upper()\n",
-    "    ont_data = [x for x in ontology_repository if ont.lower() in x][0]\n",
-    "    graph = Graph().parse(ont_data)\n",
+    "**Value Errors**  \n",
+    "*** \n",
+    "This check is performed at the individual- and merged-ontology level. This check utilizes the [`owlready2`](https://pypi.org/project/Owlready2/) library to read in each of the ontologies. This library is strict and will catch a wide variety of value errors. Should any of these errors arise, we retype the edge correctly. \n",
     "\n",
-    "    # get deprecated classes and triples\n",
-    "    dep_cls = [x[0] for x in list(graph.triples((None, OWL.deprecated, Literal('true', datatype=URIRef('http://www.w3.org/2001/XMLSchema#boolean')))))]\n",
-    "    dep_triples = [(s, p, o) for s, p, o in graph if 'deprecated' in ', '.join([str(s).lower(), str(p).lower(), str(o).lower()]) and str(s).startswith(ont_prefix)]\n",
-    "    oth_dep_triple_classes = [x[0] for x in dep_triples]\n",
-    "    deprecated_classes = set(dep_cls + oth_dep_triple_classes)\n",
-    "    # get obsolete classes and triples\n",
-    "    obs_cls = [x[0] for x in list(graph.triples((None, RDFS.subClassOf, URIRef('http://www.geneontology.org/formats/oboInOwl#ObsoleteClass'))))]\n",
-    "    obs_triples = [(s, p, o) for s, p, o in graph if 'obsolete' in ', '.join([str(s).lower(), str(p).lower(), str(o).lower()]) and str(s).startswith(ont_prefix)]\n",
-    "    oth_obs_triple_classes = [x[0] for x in obs_triples]\n",
-    "    obsolete_classes = set(obs_cls + oth_obs_triple_classes)\n",
-    "    \n",
-    "    # remove deprecated/obsolete classes\n",
-    "    for node in list(deprecated_classes) + list(obsolete_classes):        \n",
-    "        graph.remove((node, None, None))  # remove all triples about node\n",
-    "        graph.remove((None, None, node))  # remove all triples pointing to node\n",
-    "        \n",
-    "    # remove deprecated/obsolete triples\n",
-    "    for triple in dep_triples + obs_triples:        \n",
-    "        graph.remove(triple)\n",
+    "*Example Findings*  \n",
+    "The [Cell Line Ontology](http://www.clo-ontology.org/) yield the following error message:\n",
     "\n",
-    "    print('Removed {} obsolete classes and {} deprecated classes\\n'.format(len(obsolete_classes), len(deprecated_classes)))\n",
-    "    \n",
-    "    # serialize graph\n",
-    "    graph.serialize(destination=ont_data, format='xml')\n",
-    "    ontology_file_formatter(write_location, '/' + ont_data.split('/')[-1], './pkt_kg/libs/owltools')\n",
-    "    "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "<br>\n",
+    "```python\n",
+    "ValueError: invalid literal for int() with base 10: '永生的乳腺衍生细胞系细胞'\n",
+    "...\n",
+    "OwlReadyOntologyParsingError: RDF/XML parsing error in file clo_with_imports.owl, line 10970, column 99.\n",
+    "```\n",
     "\n",
-    "***\n",
-    "## MERGE ONTOLOGIES <a class=\"anchor\" id=\"merge-ontologies\"></a>\n",
-    "***\n",
+    "This tells us that we need to repair the triple containing the Literal '永生的乳腺衍生细胞系细胞' by removing it and redefining it as a `string`, rather than an `int` as it is currently defined as. This is currently noted as an issue in the [Cell Line Ontology's](http://www.clo-ontology.org/) GitHub repo ([issue #48](https://github.com/CLO-ontology/CLO/issues/48)). \n",
     "\n",
-    "**Purpose:** In this step, the [`OWLTools`](https://github.com/owlcollab/owltools) library is designed to merge a directory of ontology files into a single ontology file. This merged ontology file is required as input to the knowledge graph build algorithm.  \n",
+    "<br>\n",
     "\n",
-    "**Inputs:** A directory of ontology files (`.owl`)  \n",
-    "**Outputs:** [`PheKnowLator_MergedOntologies.owl`](https://www.dropbox.com/s/1lhh4hdwbjzds74/PheKnowLator_MergedOntologiesGeneID_Normalized_Cleaned.owl?dl=1)\n",
+    "**Identifier Errors**  \n",
+    "*** \n",
+    "This check is performed at the individual- and merged-ontology level. Check class identifiers to ensure consistency in identifier prefixes. For example, we want to identifiers that are incorrectly formatted like occurrences of `PRO_XXXXXXX` which should be `PR_XXXXXXX`. For all detected errors, we reformat the incorrectly formatted class identifiers. This is a tricky task to do in an automated manner and is something that should be updated if any new ontologies are added to the `PheKnowLator` build. Currently, the code below checks and logs any hits, but only fixes the following known errors: Vaccine Ontology: `PRO` which should be `PR`.\n",
     "\n",
-    "#### Findings\n",
+    "*Example Findings*  \n",
+    "Running this check revealed mislabeling of `2` [pROtein Ontology](https://proconsortium.org/) identifiers in the [Vaccine Ontology](http://www.violinet.org/vaccineontology/) (see [this](https://github.com/vaccineontology/VO/issues/4) GitHub issue).\n",
     "\n",
-    "The merge set of ontologies contained `366,828` classes, `3,841,408` axioms `818` object properties, and `151` individuals."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 28,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "\n",
-      "Merging Ontologies: vo_with_imports.owl, so_with_imports.owl\n",
-      "\n",
-      "Merging Ontologies: ro_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: pw_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: mondo_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "None\n",
-      "\n",
-      "Merging Ontologies: human_pro_closed.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: hp_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: go_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: ext_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "None\n",
-      "\n",
-      "Merging Ontologies: clo_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "Merging Ontologies: chebi_lite_with_imports.owl, PheKnowLator_MergedOntologies.owl\n",
-      "\n",
-      "The knowledge graph contains 366828 classes, 3841408 axioms, 818 object properties, and 151 individuals\n",
-      "\n"
-     ]
-    }
-   ],
-   "source": [
-    "# merge ontologies\n",
-    "if write_location + merged_ontology_file in glob.glob(write_location + '/*.owl'):\n",
-    "    graph = Graph().parse(write_location + merged_ontology_file)\n",
-    "    gets_ontology_statistics(write_location + merged_ontology_file)\n",
-    "else:\n",
-    "    merges_ontologies(ontology_repository, write_location, merged_ontology_file)\n",
-    "    gets_ontology_statistics(write_location + merged_ontology_file)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# use owlready2 to read in merged ontology file\n",
-    "merged_onts = get_ontology(write_location + merged_ontology_file).load()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
     "<br>\n",
     "\n",
-    "***\n",
-    "## Normalize Classes <a class=\"anchor\" id=\"normalize-classes\"></a>\n",
-    "***\n",
+    "**Obsolete and/or Deprecated Entities**  \n",
+    "*** \n",
+    "This check is only performed at the individual-ontology level. To make sure that the ontology only contains current information, all obsolete classes and any triples that they participate in are removed from the ontologies. In addition to running the code below, it may also be necessary to check for classes that are a sub-class of `oboInOwl:ObsoleteClass`, as well as any obsolete or deprecated annotations, individuals, or `owl:ObjectProperty`.\n",
     "\n",
-    "**Purpose:** The goal of this section is to checked the cleaned merged ontology file to ensure that there is consistency between the existing classes. To do this, we check two things: (1) [Aligning Existing Ontology Classes](#aligning-existing-ontologies); and (2) [Aligning Ontology Classes and New Edge Data](#aligning-new-data). More details for each type of checkare provided below.\n",
-    "  \n",
+    "<br>\n",
     "\n",
-    "**Dependencies:** The Merged Gene, RNA, Protein Map ([`Merged_gene_rna_protein_identifiers.pkl`](https://www.dropbox.com/s/6idnt7b3i322hlh/Merged_gene_rna_protein_identifiers.pkl?dl=1)) we generated in order to map genomic identifier data sources.\n",
+    "**Normalization Errors**   \n",
+    "*** \n",
+    "These checks are performed at the individual- and merged-ontology levels. There are two types of checks that are performed:  \n",
     "\n",
-    "<br>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Aligning Existing Ontology Classes <a class=\"anchor\" id=\"aligning-existing-ontologies\"></a>\n",
-    "***\n",
+    "*Normalize Duplicate Ontology Concepts*  \n",
+    "we want to make sure that all classes that represent the same entity are connected to each other. For example, consider the following: the [Sequence Ontology](http://www.sequenceontology.org/), [ChEBI](https://www.ebi.ac.uk/chebi), and [PRotein Ontology](https://proconsortium.org/) all include terms for protein, but none of these classes are connected to each other. The solution to fixing these errors is to choose a primary concept for all duplicate scenarios and make duplicate concepts an `RDFS:subClassOf` the primary concept.\n",
     "\n",
-    "**Purpose:** For this check, we want to make sure that all classes that represent the same entity are connected to each other. For example, consider the following:  \n",
-    "- Ontologies: [Sequence Ontology](http://www.sequenceontology.org/), [ChEBI](https://www.ebi.ac.uk/chebi), and [PRotein Ontology](https://proconsortium.org/) all include terms for protein, but none of these classes are connected to each other. \n",
+    "*Normalize Existing Ontology Classes*  \n",
+    "Checks for inconsistencies in ontology classes that overlap with non-ontology entity identifiers (e.g. if HP includes `HGNC` identifiers, but PheKnowLator utilizes `Entrez` identifiers). While there are other types of identifiers, we focus primarily on resolving the genomic types, since we have a master dictionary we can used to help with this ([`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/Merged_gene_rna_protein_identifiers.pkl)). This check can be updated in future iterations to include other types of identifiers, but given our detailed examination of the `v2.0.0` ontologies, these were the identifier types that needed repair.\n",
     "\n",
-    "#### Findings  \n",
-    "*Normalize Duplicate Ontology Concepts*  \n",
+    "*Example Findings*  \n",
     "The follow classes occur in all of the ontologies used in the current build and have to be normalizesd so that there are not multiple versions of the same concept:  \n",
     "\n",
     "- Gene: [VO](http://purl.obolibrary.org/obo/OGG_0000000002)  \n",
@@ -773,41 +208,47 @@
     "- Hormone: [VO](http://purl.obolibrary.org/obo/FMA_12278) \n",
     "  - <u>Solution</u>: Make the `VO` imported `FMA` class a subclass of the `CHEBI` hormone term\n",
     "\n",
-    "*Normalize Existing Ontology Classes*  \n",
-    "`19,820` `HGNC` identifiers were found and need to be converted to `Entrez` gene identifiers, which is the identifier type for genes utilized by the majority of ontologies.\n",
+    "<br>\n",
     "\n",
+    "**Punning Errors** \n",
+    "*** \n",
+    "This check is performed at the individual- and merged-ontology level. [Punning](https://www.w3.org/2007/OWL/wiki/Punning) or redeclaration errors occur for a few different reasons, but the primary or most prevalent cause observed in the ontologies used in `PheKnowLator` is due to an `owl:ObjectProperty` being incorrectly redeclared as an `owl:AnnotationProperty` or an `owl:Class` also being defined as an `OWL:ObjectProperty`. Consistent with the solution described [here](https://github.com/oborel/obo-relations/issues/130), for `owl:ObjectProperty` redeclarations we remove all `owl:AnnotationProperty` declarations. For all `owl:Class` redeclarations, we remove all `owl:ObjectProperty` redeclarations. We also handle all classes that are directly types as an `owl:Class` and an `owl:NamedIndividual` by before the `owl:NamedIndividual` assertion.\n",
     "\n",
-    "**Solution:**   \n",
-    "*Normalize Duplicate Ontology Concepts*  \n",
-    "Choose a primary concept for all duplicate scenarios and make duplicate concepts an `RDFS:subClassOf` the primary concept.\n",
+    "*Example Findings* \n",
+    "The [Cell Line Ontology](http://www.clo-ontology.org/) had 7 object properties that were illegally redeclared and triggered punning errors. More details regarding these errors are shown below. \n",
     "\n",
-    "*Normalize Existing Ontology Classes*   \n",
-    "Convert all classes that reference an HGNC gene (`n=19,820`) to an Entrez identifier. To do this, we will utilize the genomic identifier mapping information ([`Merged_gene_rna_protein_identifiers.pkl`](https://www.dropbox.com/s/6idnt7b3i322hlh/Merged_gene_rna_protein_identifiers.pkl?dl=1)) we constructed in the [`Data_Preparation.ipynb`](https://github.com/callahantiff/PheKnowLator/blob/master/Data_Preparation.ipynb) Jupyter notebook. Note that we are only updating identifiers and not verifying labels or other metadata.\n",
-    "  "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "#### *Load Merged Ontology Data*"
+    "```bash\n",
+    "2020-12-03 20:57:15,616 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002091 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002091>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002091>))]\n",
+    "2020-12-03 20:57:15,619 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000062 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000062>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000062>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000063 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000063>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000063>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002222 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002222>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002222>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0000087 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0000087>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0000087>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002161 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002161>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002161>))]\n",
+    "```\n",
+    "\n",
+    "From this message, we can see that we need to remove the following `owl:ObjectProperty` redeclared to `owl:AnnotationProperty`: `RO_0002091`, `BFO_0000062`, `BFO_0000063`, `RO_0002222`, `RO_0000087`, `RO_0002161`. There were also 2 classes (i.e. `CLO_0054407` and `CLO_0054409`) defined as being a `owl:Class` and an `owl:ObjectProperty`. This is currently noted as an issue in the Cell Line Ontology's GitHub repo [issue #43](https://github.com/CLO-ontology/CLO/issues/43)).\n",
+    "\n",
+    "NOTE. Even though a run will yield a clean a result, reading back in the ontology using any OWL API-based tools (e.g. OWL Tools) will result in a single punning error for `RO_0002161`."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "# read in merged data\n",
-    "merged_onts = Graph().parse(write_location + merged_ontology_file)"
+    "# instantiate and set-up class\n",
+    "ont_data = OntologyCleaner('', '', '', write_location)\n",
+    "\n",
+    "# updating ontology info dictionary\n",
+    "ont_data.ontology_info = {k.split('/')[-1]: {} for k, v in ont_data.ontology_info.items()}"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Normalize Duplicate Ontology Concepts**"
+    "### Individual-Ontology Cleaning"
    ]
   },
   {
@@ -816,47 +257,48 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# fix gene class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/OGG_0000000002'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/SO_0000704')))\n",
-    "\n",
-    "# fix protein class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/PR_000000001'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/SO_0000104')))\n",
-    "\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/CHEBI_36080'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/SO_0000104')))\n",
-    "\n",
-    "# fix disorder class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/OGMS_0000045'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/DOID_4')))\n",
-    "\n",
-    "# fix antigen class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/OBI_1110034'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/CHEBI_59132')))\n",
-    "\n",
-    "# fix gelatin class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/VO_0003030'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/CHEBI_5291')))\n",
-    "\n",
-    "# fix hormone class inconsistencies\n",
-    "graph.add((URIRef('http://purl.obolibrary.org/obo/FMA_12278'),\n",
-    "           URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),\n",
-    "           URIRef('http://purl.obolibrary.org/obo/CHEBI_24621')))\n",
-    "                        "
+    "# clean data\n",
+    "for ont in ont_data.ontology_info.keys():\n",
+    "    print('\\n#### Processing Ontology: {} ####'.format(ont.upper()))\n",
+    "    ont_data.ont_file_location = ont\n",
+    "    ont_data.ont_graph = Graph().parse(ont_data.temp_dir + '/' + ont_data.ont_file_location)\n",
+    "    \n",
+    "    # get starting statistics\n",
+    "    ont_data.updates_ontology_reporter()\n",
+    "    \n",
+    "    # clean ontologies\n",
+    "    ont_data.fixes_ontology_parsing_errors()\n",
+    "    ont_data.fixes_identifier_errors()\n",
+    "    \n",
+    "    \n",
+    "    \n",
+    "    \n",
+    "    .000\n",
+    "    ont_data.removes_deprecated_obsolete_entities()\n",
+    "    ont_data.fixes_punning_errors()\n",
+    "    \n",
+    "    # get finishing statistics\n",
+    "    ont_data.updates_ontology_reporter()\n",
+    "    \n",
+    "    # run cleaned ontology through the elk reasoner\n",
+    "    logically_verifies_cleaned_ontologies(ont_data.ont_graph, ont_data.temp_dir, ont_data.ont_file_location, ont_data.owltools_location)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Normalize Existing Ontology Classes**"
+    "<br>\n",
+    "\n",
+    "***\n",
+    "## MERGE ONTOLOGIES <a class=\"anchor\" id=\"merge-ontologies\"></a>\n",
+    "***\n",
+    "\n",
+    "**Purpose:** In this step, the [`OWLTools`](https://github.com/owlcollab/owltools) library is designed to merge a directory of ontology files into a single ontology file. This merged ontology file is required as input to the knowledge graph build algorithm.  \n",
+    "\n",
+    "**Inputs:** A directory of ontology files (`.owl`)\n",
+    "\n",
+    "**Outputs:** [`PheKnowLator_MergedOntologies.owl`](https://www.dropbox.com/s/1lhh4hdwbjzds74/PheKnowLator_MergedOntologiesGeneID_Normalized_Cleaned.owl?dl=1)\n"
    ]
   },
   {
@@ -865,16 +307,17 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# find all classes in graph\n",
-    "kg_classes = merged_onts.query(\n",
-    "    \"\"\"SELECT DISTINCT ?c\n",
-    "           WHERE {?c rdf:type owl:Class . }\n",
-    "           \"\"\", initNs={'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',\n",
-    "                        'owl': 'http://www.w3.org/2002/07/owl#'}\n",
-    ")\n",
+    "ont_data.ont_file_location = ont_data.merged_ontology_filename\n",
     "\n",
-    "# convert results to list of classes and only keep hgnc identifiers\n",
-    "class_list_gene = [res[0] for res in kg_classes if isinstance(res[0], URIRef) and 'hgnc' in str(res[0])]\n"
+    "# reorder list of ontology files to prepare for merging\n",
+    "onts = [ont_data.temp_dir + '/' + x for x in list(ont_data.ontology_info.keys())\n",
+    "        if x != ont_data.merged_ontology_filename]\n",
+    "\n",
+    "# merge ontologies\n",
+    "merges_ontologies(onts, ont_data.temp_dir + '/', ont_data.ont_file_location)\n",
+    "\n",
+    "# get stats on merged ontologies\n",
+    "gets_ontology_statistics(ont_data.temp_dir + '/' + ont_data.ont_file_location)"
    ]
   },
   {
@@ -883,28 +326,18 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# load genomic identifier mapping dictionary\n",
-    "genomic_id_map = pickle.load(open('resources/processed_data/Merged_gene_rna_protein_identifiers.pkl', 'rb'), encoding='bytes')\n"
+    "# load merged ontologies into RDF Lib Graph object\n",
+    "ont_data.ont_graph = Graph().parse(ont_data.temp_dir + '/' + ont_data.ont_file_location)\n",
+    "\n",
+    "# add merged ontology to dict\n",
+    "ont_data.ontology_info[ont_data.ont_file_location] = {}"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "# loop over each gene class and get entrez gene id equivalent\n",
-    "matches, not_matched = {}, []\n",
-    "gene_url = 'https://www.ncbi.nlm.nih.gov/gene/'\n",
-    "\n",
-    "for gene_class in class_list_gene:\n",
-    "    key = 'hgnc_id_' + str(gene_class).split('=')[-1]\n",
-    "    \n",
-    "    if key in genomic_id_map.keys() and any(x for x in genomic_id_map[key] if x.startswith('entrez_id')):        \n",
-    "        matches[str(gene_class)] = [gene_url + x.split('_')[-1] for x in genomic_id_map[key] if 'entrez_id' in x]\n",
-    "    else:\n",
-    "        not_matched.append(gene_class)\n",
-    "        "
+    "### Clean Merged Ontologies\n"
    ]
   },
   {
@@ -913,37 +346,25 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# print non-matching gene uris\n",
-    "not_matched\n"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "**Investigate UnMatched Genes**  \n",
-    "Only 3 of the HGNC genes were not found in our dictionary ([`HGNC:24033`](http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=24033), [`HGNC:31447`](http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=31447), [`HGNC:33870`](http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=33870)). Investigating these revealed that HGNC made these identifiers obsolete and replaced them with new identifiers. Until this term is updated in the PRO ontology, we have to manually make the same fix. \n",
+    "# get starting statistics\n",
+    "ont_data.updates_ontology_reporter()\n",
     "\n",
-    "Additionally, there were 8 HGNC ids that have all been withdrawn (i.e. [`HGNC:26619`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/26619), [`HGNC:13392`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/13392), [`HGNC:31424`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/31424), [`HGNC:8103`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/8103), \n",
-    "[`HGNC:25943`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/25943), [`HGNC:16957`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/16957), [`HGNC:23418`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/23418), [`HGNC:32021`](https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/32021)) and for our purposes, will be removed. _Note_. Please verify the output file to ensure that no errors were added when removing these genes (as mentioned in a prior cell, these `8` genes were each deleted from the PRO ontology prior to merging).\n",
+    "# clean merged ontologies\n",
+    "ont_data.fixes_identifier_errors()\n",
+    "ont_data.normalizes_duplicate_classes()\n",
+    "ont_data.normalizes_existing_classes()\n",
+    "ont_data.fixes_punning_errors()\n",
     "\n",
-    "This issue has been reported to [PRotein Ontology](https://proconsortium.org/pro.shtml) (see [this](https://github.com/PROconsortium/PRoteinOntology/issues/176) GitHub issue)."
+    "# get finishing statistics\n",
+    "ont_data.updates_ontology_reporter()"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "# investigate HGNC genes with no mappings to Entrez\n",
-    "not_matched\n",
-    "\n",
-    "# update mapping dictionary\n",
-    "gene_url = 'https://www.ncbi.nlm.nih.gov/gene/'\n",
-    "matches['http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=24033'] =  [gene_url + x.split('_')[-1] for x in genomic_id_map['hgnc_id_26545'] if 'entrez_id' in x]\n",
-    "matches['http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=31447'] =  [gene_url + x.split('_')[-1] for x in genomic_id_map['hgnc_id_16932'] if 'entrez_id' in x]\n",
-    "matches['http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=33870'] =  [gene_url + x.split('_')[-1] for x in genomic_id_map['hgnc_id_20667'] if 'entrez_id' in x]\n"
+    "### Output and Save Results\n",
+    "The cleaned merged ontology file is saved to the `resources/knowledge_graphs` directory where it can be detected by the `PheKnowLator` algorithm during the build process."
    ]
   },
   {
@@ -952,63 +373,16 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# updated gene identifiers in graph\n",
-    "for edge in graph:\n",
-    "    if str(edge[0]) in matches.keys():\n",
-    "        for mapped_id in matches[str(edge[0])]:\n",
-    "            graph.add((URIRef(mapped_id), edge[1], edge[2]))\n",
-    "            graph.remove(edge)\n",
-    "    elif str(edge[2]) in matches.keys():\n",
-    "        for mapped_id in matches[str(edge[2])]:\n",
-    "            graph.add((edge[0], edge[1], URIRef(mapped_id)))\n",
-    "            graph.remove(edge)\n",
-    "    else:\n",
-    "        continue\n",
-    "    "
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Aligning Existing Ontology Classes and New Edge Data<a class=\"anchor\" id=\"aligning-new-data\"></a>\n",
-    "***\n",
-    "\n",
-    "**Purpose:** For this check, we want to make sure that any of the existing ontology classes can be aligned with any of the new data entities that we want to add to the knowledge graph. For example:  \n",
-    "  - Gene Classes: there are several gene classes that use [HGNC](https://www.genenames.org/) identifiers. We also want to add genes, but prefer to use [Entrez gene](https://www.ncbi.nlm.nih.gov/gene) identifiers. In order to be used with our data, we must first normalize all of the HPO gene classes to Entrez gene identifiers.\n",
-    "\n",
-    "#### Findings  \n",
-    "From looking into the knowledge graph we identified `20` classes that existed in the new edge list and in the merged ontologies, but that had differing URIs (`http://www.ncbi.nlm.nih.gov/` vs. `https://www.ncbi.nlm.nih.gov/`). Those classes were:  \n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/100129307`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/100131107`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/101927789`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/102723383`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/105373297`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/107987235`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/140606`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/157285`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/163404`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/390928`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/392490`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/50810`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/51714`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/54886`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/58515`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/64748`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/79948`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/83642`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/84717`\n",
-    "- `http://www.ncbi.nlm.nih.gov/gene/9890`\n",
-    "\n",
-    "**Solution:** Replace the occurrences of `HTTP` URLs with `HTTPS`.  "
+    "ont_data.ont_graph.serialize(destination=knowledge_graphs_location + '/' + ont_data.ont_file_location, format='xml')\n",
+    "ontology_file_formatter(knowledge_graphs_location, '/' + ont_data.ont_file_location, ont_data.owltools_location)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Read in Master Edge List**  \n",
-    "The [`master edge list`](https://www.dropbox.com/s/t8sgzd847t1rof4/Master_Edge_List_Dict.json?dl=1) that is created in Step 2 of the `pkt_kg` algorithm is read in and processed into a dictionary."
+    "#### Save Ontology Cleaning Results  \n",
+    "To view the results of the ontology cleaning process print the `ont_data.ontology_info` dictionary. This dictionary is keyed by ontology filename and contains a separate dictionary for each ontology with descriptions of the results for each error check that is performed at the individual- and merged-ontology level. The results are also saved to `resources/ontologies/ontology_cleaning_report.txt`."
    ]
   },
   {
@@ -1017,28 +391,45 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# read in master edge list\n",
-    "edge_data = json.load(open('./resources/Master_Edge_List_Dict.json', 'r'))\n",
-    "\n",
-    "# convert to dictionary\n",
-    "edge_dict = dict()\n",
-    "\n",
-    "# iterate over master edges to \n",
-    "for k in edge_data:\n",
-    "    rel = edge_data[k]['uri']\n",
-    "    for edge in edge_data[k]['edge_list']:\n",
-    "        for x in edge:\n",
-    "            if x in edge_dict.keys():\n",
-    "                edge_dict[x] |= {rel[edge.index(x)]}\n",
-    "            else:\n",
-    "                edge_dict[x] = {rel[edge.index(x)]}\n"
+    "# save output locally\n",
+    "ont_order = sorted([x for x in ont_data.ontology_info.keys() if not x.startswith('Phe')]) + [ont_data.ont_file_location]\n",
+    "with open(ont_data.temp_dir + '/ontology_cleaning_report.txt', 'w') as o:\n",
+    "    o.write('=' * 50 + '\\n{}'.format('ONTOLOGY CLEANING REPORT'))\n",
+    "    o.write('\\n{}\\n'.format(str(datetime.datetime.utcnow().strftime('%a %b %d %X UTC %Y'))) + '=' * 50 + '\\n\\n')\n",
+    "    for key in ont_order:\n",
+    "        o.write('\\nONTOLOGY: {}\\n'.format(key))\n",
+    "        x = ont_data.ontology_info[key]\n",
+    "        if 'Original GCS URL' in x.keys(): o.write('\\t- Original GCS URL: {}\\n'.format(x['Original GCS URL']))\n",
+    "        if 'Processed GCS URL' in x: o.write('\\t- Processed GCS URL: {}\\n'.format(x['Processed GCS URL']))\n",
+    "        o.write('\\t- Statistics:\\n\\t\\t- Before Cleaning: {}\\n'.format(x['Starting Statistics']))\n",
+    "        if 'Final Statistics' in x.keys(): o.write('\\t\\t- After Cleaning: {}\\n'.format(x['Final Statistics']))\n",
+    "        if 'ValueErrors' in x.keys(): o.write('\\t- Value Errors: {}\\n'.format(x['ValueErrors']))\n",
+    "        if 'IdentifierErrors' in x.keys(): o.write('\\t- Identifier Errors: {}\\n'.format(x['IdentifierErrors']))\n",
+    "        if 'Deprecated' in x.keys(): o.write('\\t- Deprecated Classes: {}\\n'.format(x['Deprecated']))\n",
+    "        if 'Obsolete' in x.keys(): o.write('\\t- Obsolete Classes: {}\\n'.format(x['Obsolete']))\n",
+    "        o.write('\\t- Punning Error:\\n\\t\\t- Classes:\\n')\n",
+    "        if x['PunningErrors - Classes'] != 'None':\n",
+    "            for i in x['PunningErrors - Classes'].split(', '): o.write('\\t\\t\\t- {}\\n'.format(i))\n",
+    "        else: o.write('\\t\\t\\t- {}\\n'.format(x['PunningErrors - Classes']))\n",
+    "        o.write('\\t\\t- ObjectProperties:\\n')\n",
+    "        if x['PunningErrors - ObjectProperty'] != 'None':\n",
+    "            for i in x['PunningErrors - ObjectProperty'].split(', '): o.write('\\t\\t\\t- {}\\n'.format(i))\n",
+    "        else: o.write('\\t\\t\\t- {}\\n'.format(x['PunningErrors - ObjectProperty']))\n",
+    "        if 'Normalized - Duplicates' in x.keys():\n",
+    "            o.write('\\t- Entity Normalization:\\n')\n",
+    "            if x['Normalized - Duplicates'] != 'None':\n",
+    "                for i in x['Normalized - Duplicates'].split(', '): o.write('\\t\\t- {}\\n'.format(i))\n",
+    "            else: o.write('\\t\\t- {}\\n'.format(x['Normalized - Duplicates']))\n",
+    "            o.write('\\t\\t- Other Classes that May Need Normalization: {}\\n'.format(x['Normalized - NonOnt']))\n",
+    "            o.write('\\t\\t- Normalized HGNC IDs: {}\\n'.format(x['Normalized - Gene IDs']))\n",
+    "o.close()"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "*Process merged Ontologies and Verify New Edge Relations*"
+    "#### Clean-Up Environment"
    ]
   },
   {
@@ -1047,33 +438,31 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "for edge in graph:\n",
-    "    if str(edge[0]).startswith('http://www.ncbi.nlm.nih.gov/gene/'):\n",
-    "        updated_subj = str(edge[0]).replace('http://www.ncbi.nlm.nih.gov/gene/', 'https://www.ncbi.nlm.nih.gov/gene/')\n",
-    "        graph.add((URIRef(updated_subj), edge[1], edge[2]))\n",
-    "        graph.remove(edge)\n",
-    "        \n",
-    "    if str(edge[2]).startswith('http://www.ncbi.nlm.nih.gov/gene/'):\n",
-    "        updated_obj = str(edge[2]).replace('http://www.ncbi.nlm.nih.gov/gene/', 'https://www.ncbi.nlm.nih.gov/gene/')\n",
-    "        graph.add((edge[0], edge[1], URIRef(updated_obj)))\n",
-    "        graph.remove(edge)\n",
-    "            "
+    "# remove temp file in resources/ontologies\n",
+    "os.remove(write_location + '/' + ont_data.ont_file_location)\n",
+    "os.remove(write_location + '/Merged_gene_rna_protein_identifiers.pkl')"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "# save normalized ontology\n",
-    "graph.serialize(destination=write_location + merged_ontology_file[:-4] + 'GeneID_Normalized_Cleaned.owl', format='xml')\n",
     "\n",
-    "# apply OWL API formatting to file\n",
-    "ontology_file_formatter(write_location, merged_ontology_file[:-4] + 'GeneID_Normalized_Cleaned.owl')\n",
+    "<br>\n",
+    "\n",
+    "***\n",
+    "***\n",
     "\n",
-    "# get ontology stats\n",
-    "gets_ontology_statistics(write_location + merged_ontology_file[:-4] + 'GeneID_Normalized_Cleaned.owl')\n"
+    "```\n",
+    "@misc{callahan_tj_2019_3401437,\n",
+    "  author       = {Callahan, TJ},\n",
+    "  title        = {PheKnowLator},\n",
+    "  month        = mar,\n",
+    "  year         = 2019,\n",
+    "  doi          = {10.5281/zenodo.3401437},\n",
+    "  url          = {https://doi.org/10.5281/zenodo.3401437}\n",
+    "}\n",
+    "```"
    ]
   }
  ],
diff --git a/README.rst b/README.rst
index 894e56d23..9eff3108a 100644
--- a/README.rst
+++ b/README.rst
@@ -29,30 +29,39 @@ Through this repository we provide the following:
 How do I Learn More?
 **********************
 
-Please see the Project `Wiki`_ for more details on available knowledge graphs, the knowledge graph construction process, algorithm, and information on the data sources used to build each knowledge graph.
+- Join an existing or start a new `Discussion`_
 
-**Prelimary Results are Available:**
-Callahan TJ, Tripodi IJ, Hunter LE, Baumgartner WA. A Framework for Automated Construction of Heterogeneous Large-Scale Biomedical Knowledge Graphs. 2020; BioRxiv `DOI: https://doi.org/10.1101/2020.04.30.071407 <https://doi.org/10.1101/2020.04.30.071407>`__
+- The Project `Wiki`_ for more details on available knowledge graphs, the knowledge graph construction process,
+algorithm, and information on the data sources used to build each knowledge graph
+
+- Preliminary results from our 2020 ISMB presentation are available `here <https://doi.org/10.1101/2020.04.30.071407>`__
 
 |
 
 Releases
 ########
 
-All code and output for each release are free to download, see `Wiki <https://github.com/callahantiff/PheKnowLator/wiki>`__ for full release archive.
+All data and output for each release are free to download from our dedicated Google Cloud Storage Bucket (GCS). All data can be downloaded from the `PheKnowLator GCS Bucket
+<https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__, which is organized by release and build..
 
 **Current Release:**
 
-- ``v2.0.0`` ➞ data and code can be directly downloaded `here <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__.
+- ``v2.0.0``
+
+  - `Build Documentation <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__
+  - `Data Access <https://console.cloud.google.com/storage/browser/pheknowlator/release_v2.0.0?project=pheknowlator>`__
 
 **Prior Releases:**
 
-- ``v1.0.0`` ➞ data and code can be directly downloaded `here <https://github.com/callahantiff/PheKnowLator/wiki/v1.0.0>`__.
+- ``v1.0.0``
+
+  - `Build Documentation <https://github.com/callahantiff/PheKnowLator/wiki/v1.0.0>`__
+  - `Data Access <https://console.cloud.google.com/storage/browser/pheknowlator/release_v1.0.0?project=pheknowlator>`__
 
 |
 
 Important Updates and Notifications
-***********************************
+########
 
 - *12/21/2020:*  We are hard at work on release ``v2.0.0`` and will announce here when the release is ready for public consumption!  
 
@@ -138,7 +147,8 @@ For assistance in creating these documents, please run the following from the ro
 
     python3 generates_dependency_documents.py
 
-Prior to running this step, make sure that all mapping and filtering data referenced in `resources/resource_info.txt`_ have been created. Please see the `Data_Preparation.ipynb`_ Jupyter Notebook for detailed examples of the steps used to build the `v2.0.0 knowledge graph <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__.
+Prior to running this step, make sure that all mapping and filtering data referenced in `resources/resource_info.txt`_ have been created or downloaded for an existing build from the `PheKnowLator GCS Bucket<https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__. To generate these data yourself, please see the `Data_Preparation.ipynb`_ Jupyter Notebook for detailed examples
+of the steps used to build the `v2.0.0 knowledge graph <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__.
 
 *Note.* To ensure reproducibility, after downloading data, a metadata file is output for the ontologies (`ontology_source_metadata.txt`_) and edge data sources (`edge_source_metadata.txt`_).
 
@@ -191,11 +201,9 @@ There are several ways to run ``pkt_kg``. An example workflow is provided below.
  kg = PartialBuild(kg_version='v2.0.0',
                    write_location='./resources/knowledge_graphs',
                    construction='subclass,
-                   edge_data='./resources/Master_Edge_List_Dict.json',
                    node_data='yes,
                    inverse_relations='yes',
-                   decode_owl='yes',
-                   kg_metadata_flag='yes')
+                   decode_owl='yes')
 
  kg.construct_knowledge_graph()
 
@@ -237,11 +245,13 @@ This repo provides 3 different of ways to run ``pkt_kg``:
 
 *DOCKER*
 
+**Pre-Built Containers:** `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator/general>`__
+
 ``pkt_kg`` can be run using a Docker instance. In order to utilize the Dockerized version of the code, please make sure that you have downloaded the newest version of `Docker <https://docs.docker.com/get-docker/>`__.
 
 There are two ways to utilize Docker with this repository:
 
-- Obtain the pre-built Docker container from `DockerHub <https://docs.docker.com/get-docker/>`__
+- Obtain the pre-built Docker container from `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator>`__
 - Build the Container
 
 |
@@ -258,7 +268,9 @@ To build the ``pkt_kg`` Docker container:
     cd /path/to/PheKnowLator (Note, this is the directory containing the Dockerfile file)
     docker build -t pkt:[VERSION] .
 
-*NOTES:* When building a container using new data sources, the only files that you should have to update are the ``pkt_kg`` input dependency documents (i.e. ``PheKnowLator/resources/resource_info.txt``, ``PheKnowLator/resources/edge_source_list.txt``, and ``PheKnowLator/resources/ontology_source_list.txt``) and the ``PheKnowLatpr/.dockerignore`` (i.e. updating the sources listed under the ``## DATA NEEDED TO BUILD KNOWLEDGE GRAPH ##`` comment, to make sure they match the file paths for all datasets used to map indeitifers listed in the ``PheKnowLator/resources/resource_info.txt`` document).
+*NOTES:* When building a container using new data sources, the only files that you should have to update are the
+``pkt_kg`` input dependency documents (i.e. ``PheKnowLator/resources/resource_info.txt``,
+``PheKnowLator/resources/edge_source_list.txt``, and ``PheKnowLator/resources/ontology_source_list.txt``) and the ``PheKnowLatpr/.dockerignore`` (i.e. updating the sources listed under the ``## DATA NEEDED TO BUILD KNOWLEDGE GRAPH ##`` comment, to make sure they match the file paths for all datasets used to map indentifiers listed in the ``PheKnowLator/resources/resource_info.txt`` document).
 
 |
 
@@ -336,7 +348,8 @@ Citing this Work
 Contact
 ########
 
-We’d love to hear from you! To get in touch with us, please `create an issue`_ or `send us an email`_ 💌
+We’d love to hear from you! To get in touch with us, please join or start a new `Discussion`_, `create an issue`_
+or `send us an email`_ 💌
 
 
 .. |ABRA| image:: https://img.shields.io/badge/ReproducibleResearch-AbraCollaboratory-magenta.svg
@@ -437,3 +450,5 @@ We’d love to hear from you! To get in touch with us, please `create an issue`_
 .. _`create an issue`: https://github.com/callahantiff/PheKnowLator/issues/new/choose
 
 .. _`send us an email`: https://mail.google.com/mail/u/0/?view=cm&fs=1&tf=1&to=callahantiff@gmail.com
+
+.. _`Discussion`: https://github.com/callahantiff/PheKnowLator/discussions
diff --git a/builds/.dockerignore b/builds/.dockerignore
new file mode 100644
index 000000000..affcc4f0f
--- /dev/null
+++ b/builds/.dockerignore
@@ -0,0 +1,2 @@
+!Dockerfile
+temp/
\ No newline at end of file
diff --git a/builds/Dockerfile b/builds/Dockerfile
new file mode 100644
index 000000000..a1050f5a5
--- /dev/null
+++ b/builds/Dockerfile
@@ -0,0 +1,13 @@
+# import phase 3 container
+#FROM callahantiff/pheknowlator:latest
+#
+#COPY *.py *.txt /PheKnowLator/builds/
+#
+#WORKDIR /PheKnowLator/builds/
+#RUN pip install -r build_requirements.txt
+#
+#ENTRYPOINT ["python", "build_phase_3.py"]
+
+FROM ubuntu:latest
+
+RUN echo "Hello world"
\ No newline at end of file
diff --git a/builds/README.md b/builds/README.md
new file mode 100644
index 000000000..0bbd5a0f2
--- /dev/null
+++ b/builds/README.md
@@ -0,0 +1,74 @@
+# PheKnowLator Builds  
+**Current Release:** [`v2.0.0`](https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0)  
+
+This directory stores the scripts and files utilized by GitHub Actions to perform the monthly knowledge graph builds. As described below the CI/CD Workflow consists of three primary phases, each which serves to process a different component of the full build process. Each of these phases and their triggers and associated scripts are briefly outlines below.
+
+## Build Phases  
+### Phase 1: Download Build Data  
+**Script(s):** `build_phase_1.py`  
+
+This phase is triggered on the same date each month and consists of the following three steps:    
+1. **Create Google Cloud Storage Bucket:** To store the build files, a new Google Cloud Storage bucket under the current release and named with the current date is created (e.g. `release_v2.0.0/build_27DEC2020`). Under this directory, the rest of the needed build directories are created. See example below:
+   ```
+    |---- *build_<<date>>/
+    |     |---- data/
+    |     |     |---- original_data/
+    |     |     |---- processed_data/   
+    |     |     |---- knowledge_graphs/  
+    |     |     |     |---- subclass_builds/
+    |     |     |     |     |---- relations_only/
+    |     |     |     |     |     |---- owl/
+    |     |     |     |     |     |---- owlnets/     
+    |     |     |     |     |---- inverse_relations/
+    |     |     |     |     |     |---- owl/
+    |     |     |     |     |     |---- owlnets/     
+    |     |     |     |---- instance_builds/
+    |     |     |     |     |---- relations_only/
+    |     |     |     |     |     |---- owl/
+    |     |     |     |     |     |---- owlnets/     
+    |     |     |     |     |---- inverse_relations/
+    |     |     |     |     |     |---- owl/
+    |     |     |     |     |     |---- owlnets/    
+   ```
+2. **Download Knowledge Graph Data:** Using the `data_to_download.txt` text file, all needed build data are downloaded to the `data/original_data` directory associated with the current build. In this file, each source to download is provided as a single URL per row.   
+3. **Upload Local Build Data:** Generate `download_metadata.txt` a document that lives in the `original_data` directory and provides provenance information on each downloaded data source.
+
+<br>
+
+### Phase 2: Preprocess Downloaded Build Data     
+**Script(s):** `build_phase_2.py`; `pkt_kg/data_preprocessing.py`; `pkt_kg/ontology_cleaning.py`   
+**Data:** `genomic_typing_dict.pkl`
+
+This phase is triggered upon the successful completion of [Phase 1](#Phase-1:-Download-Build-Data) and consists of the following three steps:  
+1. **Preprocess Linked Open Data:** Runs the `pkt_kg/data_preprocessing.py` script, which preprocesses and prepares all Linked Open Data sources (i.e. non-ontology data) needed to the build the knowledge graphs. The cleaned data are output to the `data/processed_data` directory associated with the current build along with a metadata document providing provenance information on the preprocessed and cleaned documents.      
+2. **Preprocess Ontology Data:** Runs the `pkt_kg/ontology_cleaning.py` script, which preprocesses and prepares all ontology data needed to the build the knowledge graphs. The cleaned data are output to the `data/processed_data` directory associated with the current build along with a metadata document providing provenance information on the preprocessed and cleaned documents.  
+3. **Update Input Build Dependencies:** The URLS referenced in the knowledge graph [input dependency documents](https://github.com/callahantiff/PheKnowLator/wiki/Dependencies) `resource_info.txt`, `edge_source_list.txt`, and `ontology_source_list.txt` documents are updated with the Google Cloud Storage bucket URLs for each associated 
+   preprocessed document located in the `data/processed_data` directory associated with the current build.
+   
+4. **Upload Local Build Data:** Generate `preprocessed_build_metadata.txt` a document that lives in the 
+   `processed_data` directory and provides provenance information on each downloaded data source. Also uploads the 
+   ontology data cleaning results (`ontology_cleaning_report.txt`), which provides additional insight into the errors that were cleaned for each ontology.
+
+<br>
+
+### Phase 3: Build Knowledge Graph  
+**Script(s):** `build_phase_3.py`; `complete_build.py`  
+
+This phase is triggered by the successful completion of [Phase 2](#Phase-2:-Preprocess-Downloaded-Build-Data) and is the primary step responsible for constructing the PheKnowLator knowledge graphs and consists of the following seven steps:  
+1. **Downloads Input Build Dependencies:** The `resource_info.txt`, `edge_source_list.txt`, and 
+   `ontology_source_list.txt` documents updated in [Phase 2](#Phase-2:-Preprocess-Downloaded-Build-Data) are downloaded locally and committed to the Master branch of the GitHub repository.     
+2. **Downloads Processed Data:** All data processed during [Phase 2](#Phase-2:-Preprocess-Downloaded-Build-Data) are downloaded in preparation of constructing the build Docker container.  
+3. **Build Docker Container:** The primary build Docker container is built and published to Docker Hub.  
+4. **Container Parameterization and Deployment:** GitHub Actions communicates with 
+   Google Cloud Run to duplicate the constructor container and parameterize it for each of the PheKnowLator builds allowing for the knowledge graphs to be constructed in parallel.  
+5. **Completes Build:** Waits for each container to complete and then uploads associated data to the correct Google Cloud Storage bucket associated with the current build.
+6. **Update SPARQL Endpoint:** The successfully built knowledge graphs are pushed to the GraphDB SPARQL Endpoint for 
+   public consumption (*<<TODO: ADD ENDPOINT LINK HERE>>*)   
+
+____
+
+## Important Notes  
+- The contents in this directory are updated for every release and as needed to prepare bugs and unexpected issues. Any important to changes to the workflow and associated scripts will be documented her as they arise.
+
+- Any issues that may arise during any of the Phases described above will be pushed to a build log and saved under the current build's directory in the Google Cloud Storage bucket.  
+  
\ No newline at end of file
diff --git a/builds/build_phase_1.py b/builds/build_phase_1.py
new file mode 100644
index 000000000..8c63aeccc
--- /dev/null
+++ b/builds/build_phase_1.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# import needed libraries
+import os
+import re
+import shutil
+
+from datetime import date, datetime
+from google.cloud import storage  # type: ignore
+
+from pkt_kg.__version__ import __version__
+from pkt_kg.utils import data_downloader
+
+# set environment variable -- this should be replaced with GitHub Secret for build
+os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'resources/project_keys/pheknowlator-6cc612b4cbee.json'
+
+
+def creates_build_directory_structure(bucket, release):
+    """Takes a Google Cloud Storage bucket and generates all of the folders needed for a knowledge graph build.
+
+    Args:
+        bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        release: A string containing the current pkt software release.
+
+    Returns:
+         original_data: A string containing the Google Cloud Storage bucket directory to download data to for the
+            current build.
+    """
+
+    print('Creating Build Directory Structure')
+
+    # set-up file names
+    build = 'build_' + datetime.strftime(datetime.strptime(str(date.today()), '%Y-%m-%d'), '%d%b%Y').upper()
+    folder_list = [
+        '{}/{}/data/original_data/'.format(release, build),
+        '{}/{}/data/processed_data/'.format(release, build),
+        '{}/{}/knowledge_graphs/subclass_builds/relations_only/owl/'.format(release, build),
+        '{}/{}/knowledge_graphs/subclass_builds/relations_only/owlnets/'.format(release, build),
+        '{}/{}/knowledge_graphs/subclass_builds/inverse_relations/owl/'.format(release, build),
+        '{}/{}/knowledge_graphs/subclass_builds/inverse_relations/owlnets/'.format(release, build),
+        '{}/{}/knowledge_graphs/instance_builds/relations_only/owl/'.format(release, build),
+        '{}/{}/knowledge_graphs/instance_builds/relations_only/owlnets/'.format(release, build),
+        '{}/{}/knowledge_graphs/instance_builds/inverse_relations/owl/'.format(release, build),
+        '{}/{}/knowledge_graphs/instance_builds/inverse_relations/owlnets/'.format(release, build)
+    ]
+
+    # add directories to Google Cloud Storage bucket
+    for folder in folder_list:
+        blob = bucket.blob(folder)
+        blob.upload_from_string('')
+
+    return '{}/{}/data/original_data/'.format(release, build)
+
+
+def uploads_data_to_gcs_bucket(bucket, original_data, temp_directory, filename):
+    """Takes a file name and pushes the data referenced by the filename object and stored locally in that object to
+    a Google Cloud Storage bucket.
+
+    Args:
+        bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        original_data: A string specifying the location of the original_data directory for a specific build.
+        temp_directory: A local directory where preprocessed data is stored.
+        filename: A string containing a filename.
+
+    Returns:
+        None.
+    """
+
+    print('Uploading {} to GCS bucket: {}'.format(filename, original_data))
+
+    blob = bucket.blob(original_data + filename)
+    blob.upload_from_filename(temp_directory + '/' + filename)
+
+    return None
+
+
+def get_file_metadata(url, file_location, gcs_url):
+    """Takes a file path and the original URL used to download the data and retrieves metadata on the file.
+
+    Args:
+        url: A string containing the original url used to download the data file.
+        file_location: A string pointing to a file saved locally.
+        gcs_url: A string containing a URL pointing to the original_data directory in the Google Cloud Storage bucket
+            for the current build.
+
+    Returns:
+        metadata: A list containing metadata information on the file_location input object.
+    """
+
+    metadata = [
+        'DATA INFO\n  - DOWNLOAD_URL = {}'.format(str(url)),
+        '  - DOWNLOAD_DATE = {}'.format(str(datetime.now().strftime('%m/%d/%Y'))),
+        '  - FILE_SIZE_IN_BYTES = {}'.format(str(os.stat(file_location).st_size)),
+        '  - GOOGLE_CLOUD_STORAGE_URL = {}'.format(str(gcs_url + file_location.split('/')[-1]))
+    ]
+
+    return metadata
+
+
+def writes_metadata(metadata, bucket, original_data, temp_directory):
+    """Writes the downloaded URL metadata information locally and to the original_data directory in Google Cloud
+    Storage bucket for the current build.
+
+    Args:
+        bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        original_data: A string specifying the location of the original_data directory for a specific build.
+        temp_directory: A local directory where preprocessed data is stored.
+        metadata: A nested list containing metadata information on each downloaded url.
+
+    Returns:
+        None.
+    """
+
+    filename = 'downloaded_build_metadata.txt'
+
+    with open(temp_directory + '/' + filename, 'w') as out:
+        out.write('=' * 35 + '\n{}\n'.format(str(datetime.utcnow().strftime('%a %b %d %X UTC %Y'))) + '=' * 35 + '\n\n')
+        for row in metadata:
+            for i in range(4):
+                out.write(str(row[i]) + '\n')
+
+    uploads_data_to_gcs_bucket(bucket, original_data, temp_directory, filename)
+
+    return None
+
+
+def downloads_build_data(bucket, original_data, gcs_url, temp_directory, file_loc='./builds/data_to_download.txt'):
+    """Reads in the list of data to download for the current build, downloads each object, and pushes the downloaded
+    object up to a Google Cloud Storage bucket. Once all of the data are downloaded, a metadata file object is
+    generated and pushed with the downloaded data to the original_data Google Cloud Storage bucket for the current
+    build.
+
+    Args:
+        bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        original_data: A string specifying the location of the original_data directory for a specific build.
+        gcs_url: A string containing a URL pointing to the original_data directory in the Google Cloud Storage bucket
+            for the current build.
+        temp_directory: A local directory where preprocessed data is stored.
+        file_loc: A string containing the filepath and name of the data to download for the build.
+
+    Returns:
+        None.
+    """
+
+    print('Downloading Build Data')
+
+    urls, metadata = [x.strip('\n') for x in open(file_loc, 'r').readlines() if not x.startswith('#') and x != '\n'], []
+    for url in urls:
+        if url.startswith('http://purl.obolibrary.org/obo/'):
+            file_path = temp_directory + '/' + url.split('/')[-1][:-4] + '_with_imports.owl'
+            os.system("./pkt_kg/libs/owltools {} --merge-import-closure -o {}".format(url, file_path))
+        else:
+            filename, url = url.split(', ')
+            file_path = temp_directory + '/' + re.sub('.zip|.gz', '', filename)
+            data_downloader(url, temp_directory + '/', filename)
+        metadata += [get_file_metadata(url, file_path, gcs_url)]
+        f_name = re.sub('.zip|.gz', '', file_path.replace(temp_directory + '/', ''))
+        uploads_data_to_gcs_bucket(bucket, original_data, temp_directory, f_name)
+
+    # writes metadata locally and pushes it to a Google Cloud Storage bucket
+    writes_metadata(metadata, bucket, original_data, temp_directory)
+
+    return None
+
+
+def main():
+
+    print('#' * 35 + '\nBUILD PHASE 1: DOWNLOADING BUILD DATA\n' + '#' * 35)
+
+    # create temp directory to use locally for writing data GCS data to
+    temp_dir = 'builds/temp'
+    os.mkdir(temp_dir)
+
+    ###############################################
+    # STEP 1 - INITIALIZE GOOGLE STORAGE BUCKET OBJECTS
+    storage_client = storage.Client()
+    bucket = storage_client.get_bucket('pheknowlator')
+    release = 'release_v' + __version__
+
+    # create gcs bucket directories
+    gcs_original_data = creates_build_directory_structure(bucket, release)
+
+    ###############################################
+    # STEP 2 - DOWNLOAD BUILD DATA
+    gcs_url = 'https://storage.googleapis.com/pheknowlator/{}/{}/data/original_data/'.format(release, build)
+    downloads_build_data(bucket, gcs_original_data, gcs_url, temp_dir)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/builds/build_phase_2.py b/builds/build_phase_2.py
new file mode 100644
index 000000000..1654a2550
--- /dev/null
+++ b/builds/build_phase_2.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# import needed libraries
+import fnmatch
+import glob
+import os
+import shutil
+import re
+
+from datetime import datetime
+from google.cloud import storage  # type: ignore
+
+from builds.data_preprocessing import DataPreprocessing  # type: ignore
+from builds.ontology_cleaning import OntologyCleaner  # type: ignore
+from pkt_kg.__version__ import __version__
+from pkt_kg.utils import data_downloader
+
+# set environment variable -- this should be replaced with GitHub Secret for build
+os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'resources/project_keys/pheknowlator-6cc612b4cbee.json'
+
+
+def get_file_metadata(url, file_location, gcs_url):
+    """Takes a file path and the original URL used to download the data and retrieves metadata on the file.
+
+    Args:
+        url: A string containing the original url used to download the data file.
+        file_location: A string pointing to a file saved locally.
+        gcs_url: A string containing a URL pointing to the processed_data directory in the Google Cloud Storage bucket
+            for the current build.
+
+    Returns:
+        metadata: A list containing metadata information on the file_location input object.
+    """
+
+    metadata = [
+        'DATA INFO\n  - DOWNLOAD_URL = {}'.format(str(url)),
+        '  - DOWNLOAD_DATE = {}'.format(str(datetime.now().strftime('%m/%d/%Y'))),
+        '  - FILE_SIZE_IN_BYTES = {}'.format(str(os.stat(file_location).st_size)),
+        '  - GOOGLE_CLOUD_STORAGE_URL = {}'.format(str(gcs_url + file_location.split('/')[-1]))
+    ]
+
+    return metadata
+
+
+def writes_metadata(metadata, temp_directory):
+    """Writes the downloaded URL metadata information locally and to the original_data directory in Google Cloud
+    Storage bucket for the current build.
+
+    Args:
+        metadata: A nested list containing metadata information on each downloaded url.
+        temp_directory: A local directory where preprocessed data is stored.
+        metadata: A nested list containing metadata information on each downloaded url.
+
+    Returns:
+        None.
+    """
+
+    filename = 'preprocessed_build_metadata.txt'
+    with open(temp_directory + '/' + filename, 'w') as out:
+        out.write('=' * 35 + '\n{}\n'.format(str(datetime.utcnow().strftime('%a %b %d %X UTC %Y'))) +
+                  '=' * 35 + '\n\n')
+        for row in metadata:
+            for i in range(4):
+                out.write(str(row[i]) + '\n')
+
+    lod_data.uploads_data_to_gcs_bucket(filename)
+
+    return None
+
+
+def updates_dependency_documents(gcs_url, file_url, bucket, temp_directory):
+    """Takes a dependency file url and downloads the file it points to. That file is then iterated over and all urls
+    are updated to point their current Google Cloud Storage bucket. The updated file is saved locally and pushed to
+    the processed_data directory of the current build.
+
+    Args:
+        gcs_url: A string containing a URL to the current Google Cloud Storage bucket.
+        file_url: A string containing a URL to a build dependency document.
+        bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        temp_directory: A local directory where preprocessed data is stored.
+
+    Returns:
+        None.
+    """
+
+    # set bucket information
+    bucket_path = '/'.join(gcs_url.split('/')[4:])
+    gcs_processed = [_.name.split('/')[-1] for _ in bucket.list_blobs(prefix=bucket_path + 'processed_data/')]
+    gcs_original = [_.name.split('/')[-1] for _ in bucket.list_blobs(prefix=bucket_path + 'original_data/')]
+
+    # download dependency file and read in data
+    data_downloader(file_url, temp_directory + '/')
+    filename = file_url.split('/')[-1]
+    data = open(temp_directory + '/' + filename).readlines()
+
+    # update file with current gcs bucket url
+    if filename != 'resource_info.txt':
+        with open(temp_directory + '/' + filename, 'w') as out:
+            for row in tqdm(data):
+                prefix, suffix = row.strip('\n').split(', ')
+                if '.owl' not in suffix:
+                    suffix = suffix.strip('?dl=1|.gz|.zip')
+                    d_file = suffix.split('/')[-1].strip('?dl=1|.gz|.zip')
+                    org_file = fnmatch.filter(gcs_original, suffix.split('/')[-1].replace('.', '*.'))
+                    prc_file = fnmatch.filter(gcs_processed, suffix.split('/')[-1].replace('.', '*.'))
+                    if d_file in gcs_processed: out.write(prefix + ', ' + gcs_url + 'processed_data/' + d_file + '\n')
+                    elif d_file in gcs_original: out.write(prefix + ', ' + gcs_url + 'original_data/' + d_file + '\n')
+                    elif len(prc_file) > 0: out.write(prefix + ', ' + gcs_url + 'processed_data/' + prc_file[0] + '\n')
+                    else: out.write(prefix + ', ' + gcs_url + 'original_data/' + org_file[0] + '\n')
+                if '.owl' in suffix:
+                    d_file = fnmatch.filter(gcs_processed, suffix.split('/')[-1].replace('.', '*.'))
+                    if len(d_file) > 0: out.write(prefix + ', ' + gcs_url + 'processed_data/' + d_file[0] + '\n')
+
+    # push data to bucket
+    lod_data.uploads_data_to_gcs_bucket(filename)
+
+    return None
+
+
+def main():
+    print('#' * 35 + '\nBUILD PHASE 2: DATA PRE-PROCESSING\n' + '#' * 35)
+
+    temp_dir = 'builds/temp'
+    if not os.path.exists(temp_dir): os.mkdir(temp_dir)
+
+    ###############################################
+    # STEP 1 - INITIALIZE GOOGLE STORAGE BUCKET OBJECTS
+    storage_client = storage.Client()
+    bucket = storage_client.get_bucket('pheknowlator')
+    # define write path to Google Cloud Storage bucket
+    release = 'release_v' + __version__
+    bucket_files = [file.name.split('/')[1] for file in bucket.list_blobs(prefix=release)]
+    build = 'build_' + sorted([x[0] for x in [re.findall(r'(?<=_)\d.*', x) for x in bucket_files] if len(x) > 0])[-1]
+    gcs_original_data = '{}/{}/data/{}'.format(release, build, 'original_data/')
+    gcs_processed_data = '{}/{}/data/{}'.format(release, build, 'processed_data/')
+    gcs_url = 'https://storage.googleapis.com/pheknowlator/{}/{}/data/'.format(release, build)
+
+    ###############################################
+    # STEP 2 - PREPROCESS BUILD DATA
+    lod_data = DataPreprocessing(bucket, gcs_original_data, gcs_processed_data, temp_dir)
+    lod_data.preprocesses_build_data()
+
+    ###############################################
+    # STEP 3 - CLEAN ONTOLOGY DATA
+    ont_data = OntologyCleaner(bucket, gcs_original_data, gcs_processed_data, temp_dir)
+    ont_data.cleans_ontology_data()
+
+    ###############################################
+    # STEP 4 - GENERATE METADATA DOCUMENTATION
+    metadata, processed_data = [], glob.glob(temp_dir + '/*')
+    for data_file in tqdm(processed_data):
+        url = gcs_url + '/original_data/' + data_file.replace(temp_dir + '/', '')
+        metadata += [get_file_metadata(url, data_file, gcs_url + 'processed_data/')]
+    writes_metadata(metadata, temp_dir)
+
+    ###############################################
+    # STEP 5 - UPDATE INPUT DEPENDENCY DOCUMENTS
+    # edge source list
+    edge_src_list = 'https://raw.githubusercontent.com/callahantiff/PheKnowLator/master/resources/edge_source_list.txt'
+    updates_dependency_documents(gcs_url, edge_src_list, bucket, temp_dir)
+    # ontology source list
+    ont_list = 'https://raw.githubusercontent.com/callahantiff/PheKnowLator/master/resources/ontology_source_list.txt'
+    updates_dependency_documents(gcs_url, ont_list, bucket, temp_dir)
+    # resource info
+    resources = 'https://raw.githubusercontent.com/callahantiff/PheKnowLator/master/resources/resource_info.txt'
+    updates_dependency_documents(gcs_url, resources, bucket, temp_dir)
+
+    # clean up environment after uploading all processed data
+    shutil.rmtree(temp_dir)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/builds/build_phase_3.py b/builds/build_phase_3.py
new file mode 100644
index 000000000..ce98c5d4e
--- /dev/null
+++ b/builds/build_phase_3.py
@@ -0,0 +1,11 @@
+
+def main():
+
+    print('Hello World\n\n')
+
+    with open('/output/test.txt', 'w') as o:
+        o.write('hello world')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/builds/build_requirements.txt b/builds/build_requirements.txt
new file mode 100644
index 000000000..88f30c04f
--- /dev/null
+++ b/builds/build_requirements.txt
@@ -0,0 +1,10 @@
+google==1.9.3
+google-cloud-storage==1.28.0
+networkx==2.4
+numpy==1.18.1
+openpyxl==3.0.3
+Owlready2==0.25
+pandas==1.0.5
+rdflib==4.2.2
+reactome2py==0.0.8
+tqdm==4.47.0
\ No newline at end of file
diff --git a/builds/data_preprocessing.py b/builds/data_preprocessing.py
new file mode 100644
index 000000000..9039ce4ae
--- /dev/null
+++ b/builds/data_preprocessing.py
@@ -0,0 +1,1619 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# import needed libraries
+import fnmatch
+import itertools
+import networkx  # type: ignore
+import numpy  # type: ignore
+import os
+import pandas  # type: ignore
+import pickle
+import sys
+
+from google.cloud import storage  # type: ignore
+from rdflib import Graph, Namespace, URIRef  # type: ignore
+from rdflib.namespace import RDFS  # type: ignore
+from reactome2py import content  # type: ignore
+from tqdm import tqdm  # type: ignore
+from typing import Dict, List, Optional, Tuple, Union
+
+# import script containing helper functions
+from pkt_kg.utils import *
+
+
+class DataPreprocessing(object):
+    """Class provides a container for the data preprocessing methods, original housed in the Data_Preprocessing.ipynb
+    Jupyter Notebook. See notebook (https://github.com/callahantiff/PheKnowLator/blob/master/Data_Preparation.ipynb) for
+    more detailed descriptions of each processed data source and the rationale behind the different filtering and
+    processing approaches.
+
+    Attributes:
+        gcs_bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        org_data: A string specifying the location of the original_data directory for a specific build.
+        processed_data: A string specifying the location of the original_data directory for a specific build.
+        temp_dir: A string specifying a temporary directory to use while processing data locally.
+    """
+
+    def __init__(self, gcs_bucket: storage.bucket.Bucket, org_data: str, processed_data: str, temp_dir: str) -> None:
+
+        # GOOGLE CLOUD STORAGE VARIABLES
+        self.bucket: storage.bucket.Bucket = gcs_bucket
+        self.original_data: str = org_data
+        self.processed_data: str = processed_data
+        # SETTING LOCAL VARIABLES
+        self.temp_dir = temp_dir
+        self.owltools_location = './pkt_kg/libs/owltools'
+        # OTHER CLASS VARIABLES
+        self.genomic_type_mapper: Dict = {}
+
+    def uploads_data_to_gcs_bucket(self, filename: str) -> None:
+        """Takes a file name and pushes the corresponding data referenced by the filename object from a local
+        temporary directory to a Google Cloud Storage bucket.
+
+        Args:
+            filename: A string containing the name of file to write to a Google Cloud Storage bucket.
+
+        Returns:
+            None.
+        """
+
+        blob = self.bucket.blob(self.processed_data + filename)
+        blob.upload_from_filename(self.temp_dir + '/' + filename)
+
+        return None
+
+    def downloads_data_from_gcs_bucket(self, filename: str) -> str:
+        """Takes a filename and and downloads the corresponding data to a local temporary directory, if it has not
+        already been downloaded.
+
+        Args:
+            filename: A string containing the name of file to write to a Google Cloud Storage bucket.
+
+        Returns:
+            data_file: A string containing the local filepath for a file downloaded from a GSC bucket.
+
+        Raises:
+            ValueError: when trying to download a non-existent file from the GCS original_data dir of the current build.
+        """
+
+        try:
+            _files = [_.name for _ in self.bucket.list_blobs(prefix=self.original_data)]
+            matched_file = fnmatch.filter(_files, '*/' + filename)[0]  # poor man's glob
+            data_file = self.temp_dir + '/' + matched_file.split('/')[-1]
+            if not os.path.exists(data_file):  # only download if file has not yet been downloaded
+                self.bucket.blob(matched_file).download_to_filename(self.temp_dir + '/' + matched_file.split('/')[-1])
+        except IndexError:
+            raise ValueError('Cannot find {} in the GCS original_data directory of the current build'.format(filename))
+
+        return data_file
+
+    def reads_gcs_bucket_data_to_df(self, filename: str, delm: str, skip: int = 0,
+                                    head: Optional[Union[int, List]] = None,
+                                    sht: Optional[Union[int, str]] = None) -> pandas.DataFrame:
+        """Reads data corresponding to the input file_location variable into a Pandas DataFrame.
+
+        Args:
+            filename: A string containing the name of file that exists in a Google Cloud Storage bucket.
+            delm: A string specifying a file delimiter.
+            skip: An integer specifying the number of rows to skip when reading in the data.
+            head: An integer specifying the header row, None for no header or a list of header names.
+            sht: Used for reading xlsx files. If not None, an integer or string specifying which sheet to read in.
+
+        Returns:
+             df: A Pandas DataFrame object containing data read from a Google Cloud Storage bucket.
+        """
+
+        data = self.downloads_data_from_gcs_bucket(filename)
+
+        if not isinstance(head, List):
+            if sht is not None:
+                df = pandas.read_excel(data, sheet_name=sht, header=head, skiprows=skip, engine='openpyxl')
+            else:
+                df = pandas.read_csv(data, header=head, delimiter=delm, skiprows=skip, low_memory=0)
+        else:
+            if sht is not None:
+                df = pandas.read_excel(data, sheet_name=sht, header=None, names=head, skiprows=skip, engine='openpyxl')
+            else:
+                df = pandas.read_csv(data, header=None, names=head, delimiter=delm, skiprows=skip, low_memory=0)
+
+        return df
+
+    def _loads_genomic_typing_dictionary(self) -> None:
+        """Downloads and loads genomic typing dictionary needed to process the genomic identifier data. This
+        dictionary object is keyed by specific column names in each genomic identifier Pandas DataFrame and has
+        values which are a dictionary where keys are values in the specific column and values are a new string to
+        translate that key string into.
+
+        Returns:
+            None.
+        """
+
+        data_file = self.downloads_data_from_gcs_bucket('genomic_typing_dict.pkl')
+        self.genomic_type_mapper = pickle.load(open(data_file, 'rb'))
+
+        return None
+
+    def _preprocess_hgnc_data(self) -> pandas.DataFrame:
+        """Processes HGNC data in order to prepare it for combination with other gene identifier data sources. Data
+        needs to be lightly cleaned before it can be merged with other data. This light cleaning includes renaming
+        columns, replacing NaN with None, updating data types (i.e. making all columns type str), and unnesting '|'-
+        delimited data. The final step is to update the gene_type variable such that each of the variable values is
+        re-grouped to be protein-coding, other or ncRNA.
+
+        Returns:
+            explode_df_hgnc: A Pandas DataFrame containing processed and filtered data.
+        """
+
+        hgnc = self.reads_gcs_bucket_data_to_df(filename='hgnc_complete_set.txt', delm='\t', skip=0, head=0, sht=None)
+        hgnc = hgnc.loc[hgnc['status'].apply(lambda x: x == 'Approved')]
+        hgnc = hgnc[['hgnc_id', 'entrez_id', 'ensembl_gene_id', 'uniprot_ids', 'symbol', 'locus_type', 'alias_symbol',
+                     'name', 'location', 'alias_name']]
+        hgnc.rename(columns={'uniprot_ids': 'uniprot_id', 'location': 'map_location', 'locus_type': 'hgnc_gene_type'},
+                    inplace=True)
+        hgnc['hgnc_id'].replace('.*\:', '', inplace=True, regex=True)  # strip 'HGNC' off of the identifiers
+        hgnc.fillna('None', inplace=True)  # replace NaN with 'None'
+        hgnc['entrez_id'] = hgnc['entrez_id'].apply(lambda x: str(int(x)) if x != 'None' else 'None')  # make col str
+        # combine certain columns into single column
+        hgnc['name'] = hgnc['name'] + '|' + hgnc['alias_name']
+        hgnc['synonyms'] = hgnc['alias_symbol'] + '|' + hgnc['alias_name']
+
+        # explode nested data and reformat values in preparation for combining it with other gene identifiers
+        explode_df_hgnc = explodes_data(hgnc.copy(), ['ensembl_gene_id', 'uniprot_id', 'symbol', 'name'], '|')
+
+        # reformat hgnc gene type
+        for v in self.genomic_type_mapper['hgnc_gene_type'].keys():
+            explode_df_hgnc['hgnc_gene_type'].replace(v, self.genomic_type_mapper['hgnc_gene_type'][v], inplace=True)
+        # reformat master hgnc gene type
+        explode_df_hgnc['master_gene_type'] = explode_df_hgnc['hgnc_gene_type']
+        master_dict = self.genomic_type_mapper['hgnc_master_gene_type']
+        for val in master_dict.keys():
+            explode_df_hgnc['master_gene_type'].replace(val, master_dict[val], inplace=True)
+
+        # post-process reformatted data
+        explode_df_hgnc.drop(['alias_symbol', 'alias_name'], axis=1, inplace=True)  # remove original gene type column
+        explode_df_hgnc.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return explode_df_hgnc
+
+    def _preprocess_ensembl_data(self) -> pandas.DataFrame:
+        """Processes Ensembl data in order to prepare it for combination with other gene identifier data sources. Data
+        needs to be reformatted in order for it to be able to be merged with the other gene, RNA, and protein identifier
+        data. To do this, we iterate over each row of the data and extract the fields shown below in column_names,
+        making each of these extracted fields their own column. The final step is to update the gene_type variable such
+        that each of the variable values is re-grouped to be protein-coding, other or ncRNA.
+
+        Returns:
+            ensembl_geneset: A Pandas DataFrame containing processed and filtered data.
+        """
+
+        f_name, cleaned_col = 'Homo_sapiens.GRCh38.*.gtf', []
+        ensembl_geneset = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=5, head=None, sht=None)
+        data_cols = ['gene_id', 'transcript_id', 'gene_name', 'gene_biotype', 'transcript_name', 'transcript_biotype']
+        for data_list in tqdm(list(ensembl_geneset[8])):
+            data_list = data_list if not data_list.endswith(';') else data_list[:-1]
+            temp_data = [data_list.split('; ')[[x.split(' ')[0] for x in data_list.split('; ')].index(col)]
+                         if col in data_list else col + ' None' for col in data_cols]
+            cleaned_col.append(temp_data)
+        ensembl_geneset.fillna('None', inplace=True)
+        # update primary column values
+        ensembl_geneset['ensembl_gene_id'] = [x[0].split(' ')[-1].replace('"', '') for x in cleaned_col]
+        ensembl_geneset['transcript_stable_id'] = [x[1].split(' ')[-1].replace('"', '') for x in cleaned_col]
+        ensembl_geneset['symbol'] = [x[2].split(' ')[-1].replace('"', '') for x in cleaned_col]
+        ensembl_geneset['ensembl_gene_type'] = [x[3].split(' ')[-1].replace('"', '') for x in cleaned_col]
+        ensembl_geneset['transcript_name'] = [x[4].split(' ')[-1].replace('"', '') for x in cleaned_col]
+        ensembl_geneset['ensembl_transcript_type'] = [x[5].split(' ')[-1].replace('"', '') for x in cleaned_col]
+
+        # reformat ensembl gene type
+        gene_dict = self.genomic_type_mapper['ensembl_gene_type']
+        for val in gene_dict.keys():
+            ensembl_geneset['ensembl_gene_type'].replace(val, gene_dict[val], inplace=True)
+        # reformat master gene type
+        ensembl_geneset['master_gene_type'] = ensembl_geneset['ensembl_gene_type']
+        gene_dict = self.genomic_type_mapper['ensembl_master_gene_type']
+        for val in gene_dict.keys():
+            ensembl_geneset['master_gene_type'].replace(val, gene_dict[val], inplace=True)
+        # reformat master transcript type
+        ensembl_geneset['ensembl_transcript_type'].replace('vault_RNA', 'vaultRNA', inplace=True, regex=False)
+        ensembl_geneset['master_transcript_type'] = ensembl_geneset['ensembl_transcript_type']
+        trans_dict = self.genomic_type_mapper['ensembl_master_transcript_type']
+        for val in trans_dict.keys():
+            ensembl_geneset['master_transcript_type'].replace(val, trans_dict[val], inplace=True)
+
+        # post-process reformatted data
+        ensembl_geneset.drop(list(range(9)), axis=1, inplace=True)
+        ensembl_geneset.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return ensembl_geneset
+
+    def merges_ensembl_mapping_data(self) -> pandas.DataFrame:
+        """Processes Ensembl uniprot and entrez mapping data in order to prepare it for combination with other gene
+        identifier data sources. After merging the annotation together the main gene data is merged with the
+        annotation data. The cleaned Ensembl data is saved so that it can be used when generating node metadata for
+        transcript identifiers.
+
+        Returns:
+            ensembl: A Pandas DataFrame containing processed and filtered data that has been merged with
+                additional annotation  mapping data from uniprot and entrez.
+        """
+
+        drop_cols = ['db_name', 'info_type', 'source_identity', 'xref_identity', 'linkage_type']
+        un_name, ent_name = 'Homo_sapiens.GRCh38.*.uniprot.tsv', 'Homo_sapiens.GRCh38.*.entrez.tsv'
+        # uniprot data
+        ensembl_uniprot = self.reads_gcs_bucket_data_to_df(filename=un_name, delm='\t', skip=0, head=0, sht=None)
+        ensembl_uniprot.rename(columns={'xref': 'uniprot_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)
+        ensembl_uniprot.replace('-', 'None', inplace=True)
+        ensembl_uniprot.fillna('None', inplace=True)
+        ensembl_uniprot.drop(drop_cols, axis=1, inplace=True)
+        # entrez data
+        ensembl_entrez = self.reads_gcs_bucket_data_to_df(filename=ent_name, delm='\t', skip=0, head=0, sht=None)
+        ensembl_entrez.rename(columns={'xref': 'entrez_id', 'gene_stable_id': 'ensembl_gene_id'}, inplace=True)
+        ensembl_entrez = ensembl_entrez.loc[ensembl_entrez['db_name'].apply(lambda x: x == 'EntrezGene')]
+        ensembl_entrez.replace('-', 'None', inplace=True)
+        ensembl_entrez.fillna('None', inplace=True)
+        ensembl_entrez.drop(drop_cols, axis=1, inplace=True)
+
+        # merge annotation data
+        mrglist = ['ensembl_gene_id', 'transcript_stable_id', 'protein_stable_id']
+        ensembl_annot = pandas.merge(ensembl_uniprot, ensembl_entrez, left_on=mrglist, right_on=mrglist, how='outer')
+        ensembl_annot.fillna('None', inplace=True)
+        # merge annotation and gene data
+        ensembl_geneset = self._preprocess_ensembl_data()
+        ensembl = pandas.merge(ensembl_geneset, ensembl_annot, left_on=mrglist[0:2], right_on=mrglist[0:2], how='outer')
+        ensembl.fillna('None', inplace=True)
+        ensembl.replace('NA', 'None', inplace=True, regex=False)
+        ensembl.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        # save data locally and push to gcs bucket
+        filename = 'ensembl_identifier_data_cleaned.txt'
+        ensembl.to_csv(self.temp_dir + '/' + filename, header=True, sep='\t', index=False)
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return ensembl
+
+    def _preprocess_uniprot_data(self) -> pandas.DataFrame:
+        """Processes Uniprot data in order to prepare it for combination with other gene identifier data sources. The
+        reformatting performed on the data includes removing unnecessary columns and reformatting column values to a
+        common set of terms that will be applied universally to all gene and protein identifier data sources.
+
+        Returns:
+            explode_df_uniprot: A Pandas DataFrame containing processed and filtered data.
+        """
+
+        f_name = 'uniprot_identifier_mapping.tab'
+        uniprot = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=0, sht=None)
+        uniprot.fillna('None', inplace=True)
+        uniprot.rename(columns={'Entry': 'uniprot_id', 'Cross-reference (GeneID)': 'entrez_id',
+                                'Ensembl transcript': 'transcript_stable_id', 'Cross-reference (HGNC)': 'hgnc_id',
+                                'Gene names  (synonym )': 'synonyms', 'Gene names  (primary )': 'symbol'}, inplace=True)
+        uniprot['synonyms'] = uniprot['synonyms'].apply(lambda x: '|'.join(x.split()) if x.isupper() else x)
+
+        # explode nested data and perform light value reformatting
+        explode_df_uniprot = explodes_data(uniprot.copy(), ['transcript_stable_id', 'entrez_id', 'hgnc_id'], ';')
+        explode_df_uniprot = explodes_data(explode_df_uniprot.copy(), ['symbol'], '|')
+        explode_df_uniprot['transcript_stable_id'].replace('\s.*', '', inplace=True, regex=True)  # strip uniprot names
+        explode_df_uniprot.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return explode_df_uniprot
+
+    def _preprocess_ncbi_data(self) -> pandas.DataFrame:
+        """Processes NCBI Gene data in order to prepare it for combination with other gene identifier data sources.
+        Data needs to be lightly cleaned before it can be merged with other data. This light cleaning includes renaming
+        columns, replacing NaN with None, updating data types (i.e. making all columns type str), and unnesting '|'-
+        delimited data. Then, the gene_type variable is cleaned such that each of the variable's values are re-grouped
+        to be protein-coding, other or ncRNA.
+
+        Returns:
+            explode_df_ncbi_gene: A Pandas DataFrame containing processed and filtered data.
+        """
+
+        f_name = 'Homo_sapiens.gene_info'
+        ncbi_gene = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=0, sht=None)
+        ncbi_gene = ncbi_gene.loc[ncbi_gene['#tax_id'].apply(lambda x: x == 9606)]  # remove non-human rows
+        ncbi_gene.replace('-', 'None', inplace=True)
+        ncbi_gene.rename(columns={'GeneID': 'entrez_id', 'Symbol': 'symbol', 'Synonyms': 'synonyms'}, inplace=True)
+        ncbi_gene['symbol'] = ncbi_gene['Symbol_from_nomenclature_authority'] + '|' + ncbi_gene['symbol']
+        ncbi_gene['name'] = ncbi_gene['Full_name_from_nomenclature_authority'] + '|' + ncbi_gene['description']
+
+        # explode nested data
+        explode_df_ncbi_gene = explodes_data(ncbi_gene.copy(), ['symbol', 'name'], '|')
+        explode_df_ncbi_gene['entrez_id'] = explode_df_ncbi_gene['entrez_id'].astype(str)
+        # reformat entrez gene type
+        explode_df_ncbi_gene['entrez_gene_type'] = explode_df_ncbi_gene['type_of_gene']
+        gene_dict = self.genomic_type_mapper['entrez_gene_type']
+        for val in gene_dict.keys():
+            explode_df_ncbi_gene['entrez_gene_type'].replace(val, gene_dict[val], inplace=True)
+        # reformat master gene type
+        explode_df_ncbi_gene['master_gene_type'] = explode_df_ncbi_gene['entrez_gene_type']
+        gene_dict = self.genomic_type_mapper['master_gene_type']
+        for val in gene_dict.keys():
+            explode_df_ncbi_gene['master_gene_type'].replace(val, gene_dict[val], inplace=True)
+
+        # post-process reformatted data
+        explode_df_ncbi_gene.drop(['type_of_gene', 'dbXrefs', 'description', 'Nomenclature_status', 'Modification_date',
+                                   'LocusTag', '#tax_id', 'Full_name_from_nomenclature_authority', 'Feature_type',
+                                   'Symbol_from_nomenclature_authority'], axis=1, inplace=True)
+        explode_df_ncbi_gene.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return explode_df_ncbi_gene
+
+    def _preprocess_protein_ontology_mapping_data(self) -> pandas.DataFrame:
+        """Processes PRotein Ontology identifier mapping data in order to prepare it for combination with other gene
+        identifier data sources.
+
+        Returns:
+            pro: A Pandas DataFrame containing processed and filtered data.
+        """
+
+        col_names = ['pro_id', 'Entry', 'pro_mapping']
+        pro = self.reads_gcs_bucket_data_to_df(filename='promapping.txt', delm='\t', skip=0, head=col_names, sht=None)
+        pro = pro.loc[pro['Entry'].apply(lambda x: x.startswith('UniProtKB:') and '_VAR' not in x and ', ' not in x)]
+        pro = pro.loc[pro['pro_mapping'].apply(lambda x: x.startswith('exact'))]
+        pro['pro_id'].replace('PR:', 'PR_', inplace=True, regex=True)  # replace PR: with PR_
+        pro['Entry'].replace('(^\w*\:)', '', inplace=True, regex=True)  # remove ids which appear before ':'
+        pro.rename(columns={'Entry': 'uniprot_id'}, inplace=True)
+        pro.drop(['pro_mapping'], axis=1, inplace=True)
+        pro.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return pro
+
+    def _merges_genomic_identifier_data(self) -> pandas.DataFrame:
+        """Merges HGNC, Ensembl, Uniprot, and PRotein Ontology identifiers into a single Pandas DataFrame.
+
+        Returns:
+            merged_data: A Pandas DataFrame of merged genomic identifier information.
+        """
+
+        print('\t- Loading, Processing, and Merging Genomic Identifier Data')
+
+        # hgnc + ensembl
+        hgnc, ensembl = self._preprocess_hgnc_data(), self.merges_ensembl_mapping_data()
+        merge_cols = ['ensembl_gene_id', 'entrez_id', 'uniprot_id', 'master_gene_type', 'symbol']
+        ensembl_hgnc = pandas.merge(ensembl, hgnc, on=merge_cols, how='outer')
+        ensembl_hgnc.fillna('None', inplace=True)
+        ensembl_hgnc.drop_duplicates(subset=None, keep='first', inplace=True)
+        # ensembl_hgnc + uniprot
+        uniprot = self._preprocess_uniprot_data()
+        merge_cols = ['entrez_id', 'hgnc_id', 'uniprot_id', 'transcript_stable_id', 'symbol', 'synonyms']
+        ensembl_hgnc_uniprot = pandas.merge(ensembl_hgnc, uniprot, on=merge_cols, how='outer')
+        ensembl_hgnc_uniprot.fillna('None', inplace=True)
+        ensembl_hgnc_uniprot.drop_duplicates(subset=None, keep='first', inplace=True)
+        # ensembl_hgnc_uniprot + Homo_sapiens.gene_info
+        ncbi = self._preprocess_ncbi_data()
+        merge_cols = ['entrez_id', 'master_gene_type', 'symbol', 'synonyms', 'name', 'map_location']
+        ensembl_hgnc_uniprot_ncbi = pandas.merge(ensembl_hgnc_uniprot, ncbi, on=merge_cols, how='outer')
+        ensembl_hgnc_uniprot_ncbi.fillna('None', inplace=True)
+        ensembl_hgnc_uniprot_ncbi.drop_duplicates(subset=None, keep='first', inplace=True)
+        # ensembl_hgnc_uniprot_ncbi + promapping.txt
+        pro = self._preprocess_protein_ontology_mapping_data()
+        merged_data = pandas.merge(ensembl_hgnc_uniprot_ncbi, pro, on='uniprot_id', how='outer')
+        merged_data.fillna('None', inplace=True)
+        merged_data.drop_duplicates(subset=None, keep='first', inplace=True)
+
+        return merged_data
+
+    def _fixes_genomic_symbols(self) -> pandas.DataFrame:
+        """Takes a Pandas DataFrame of genomic identifier data and fixes gene symbol information.
+
+        Returns:
+            merged_data_clean: A Pandas DataFrame with fix genomic symbols.
+        """
+
+        clean_dates, merged_data = [], self._merges_genomic_identifier_data()
+        for x in tqdm(list(merged_data['symbol'])):
+            if '-' in x and len(x.split('-')[0]) < 3 and len(x.split('-')[1]) == 3:
+                clean_dates.append(x.split('-')[1].upper() + x.split('-')[0])
+            else:
+                clean_dates.append(x)
+        merged_data['symbol'] = clean_dates
+
+        # make sure that all gene and transcript type columns have none recoded to unknown or not protein-coding
+        merged_data['hgnc_gene_type'].replace('None', 'unknown', inplace=True, regex=False)
+        merged_data['ensembl_gene_type'].replace('None', 'unknown', inplace=True, regex=False)
+        merged_data['entrez_gene_type'].replace('None', 'unknown', inplace=True, regex=False)
+        merged_data['master_gene_type'].replace('None', 'unknown', inplace=True, regex=False)
+        merged_data['master_transcript_type'].replace('None', 'not protein-coding', inplace=True, regex=False)
+        merged_data['ensembl_transcript_type'].replace('None', 'unknown', inplace=True, regex=False)
+        merged_data_clean = merged_data.drop_duplicates(subset=None, keep='first')
+
+        return merged_data_clean
+
+    def _cross_maps_genomic_identifier_data(self) -> Dict:
+        """Takes a Pandas Dataframe of merged genomic identifiers and expands them to create a complete mapping between
+        the identifiers. A master dictionary is built, where the keys are ensembl_gene_id, transcript_stable_id,
+        protein_stable_id, uniprot_id, entrez_id, hgnc_id, and pro_id identifiers and values are the list of identifiers
+        that match to each identifier. This function takes 40-50 minutes to complete.
+
+        Returns:
+            master_dict: A dict where keys are genomic identifiers and values are lists of other identifiers and
+                metadata mapped to that identifier.
+        """
+
+        # reformat data to convert all nones, empty values, and unknowns to NaN
+        merged_data: pandas.DataFrame = self._fixes_genomic_symbols()
+        master_dict: Dict = {}
+        for col in merged_data.columns:
+            merged_data[col] = merged_data[col].apply(lambda x: '|'.join([i for i in x.split('|') if i != 'None']))
+        merged_data.replace(to_replace=['None', '', 'unknown'], value=numpy.nan, inplace=True)
+        identifiers = [x for x in merged_data.columns if x.endswith('_id')] + ['symbol']
+        # convert data to dictionary
+        for idx in tqdm(identifiers):
+            grouped_data = merged_data.groupby(idx)
+            grp_ids = set([x for x in list(grouped_data.groups.keys()) if x != numpy.nan])
+            for grp in grp_ids:
+                df = grouped_data.get_group(grp).dropna(axis=1, how='all')
+                df_cols, key = df.columns, idx + '_' + grp
+                val_df = [[c + '_' + x for x in set(df[c]) if isinstance(x, str)] for c in df_cols if c != idx]
+                if len(val_df) > 0:
+                    if key in master_dict.keys(): master_dict[key] += [i for j in val_df for i in j if len(i) > 0]
+                    else: master_dict[key] = [i for j in val_df for i in j if len(i) > 0]
+
+        return master_dict
+
+    def creates_master_genomic_identifier_map(self) -> Dict:
+        """Identifies a master gene and transcript type for each entity because the last ran code chunk can result in
+        several genes and transcripts with differing types (i.e. protein-coding or not protein-coding). The next step
+        collects all information for each gene and transcript and performs a voting procedure to select a single
+        primary gene and transcript type.
+
+        Returns:
+            reformatted_mapped_ids: A dict containing genomic identifier information which is keyed by genomic
+                identifier types and where values are lists of all other genomic identifiers that map to that key.
+        """
+
+        print('\t- Creating Genomic Identifier Cross-Mapping Dictionary')
+
+        master_dict = self._cross_maps_genomic_identifier_data()
+        reformatted_mapped_identifiers, gene_type, transcript_type = {}, 'gene_type', 'transcript_type'
+        for key, values in tqdm(master_dict.items()):
+            g_type_list, t_type_list, g_types, t_types, identifier_info = [], [], [], [], []
+            if any(x for x in ['ensembl_gene_id', 'entrez_id', 'hgnc_id', 'symbol'] if x in key):
+                for idx in values:
+                    if gene_type in idx: g_types += [idx.split('_')[-1]]
+                    else: identifier_info += [idx]
+                for i in [g_types if len(g_types) > 0 else ['None']]:
+                    g_type_list += ['protein-coding' if 'protein-coding' in i else 'not protein-coding']
+                identifier_info += ['gene_type_update_' + max(set(g_type_list), key=g_type_list.count)]
+            elif 'transcript' in key:
+                for idx in values:
+                    if transcript_type in idx: t_types += [idx.split('_')[-1]]
+                    else: identifier_info += [idx]
+                for i in [t_types if len(t_types) > 0 else ['None']]:
+                    t_type_list += ['protein-coding' if 'protein-coding' in i else 'not protein-coding']
+                identifier_info += ['transcript_type_update_' + max(set(t_type_list), key=t_type_list.count)]
+            else:
+                identifier_info = values
+            reformatted_mapped_identifiers[key] = identifier_info
+
+        # save results for output > 4GB requires special approach: https://stackoverflow.com/questions/42653386
+        filename = 'Merged_gene_rna_protein_identifiers.pkl'
+        with open(self.temp_dir + '/' + filename, 'wb') as f_out:
+            for idx in range(0, sys.getsizeof(pickle.dumps(reformatted_mapped_identifiers)), 2 ** 31 - 1):
+                f_out.write(pickle.dumps(reformatted_mapped_identifiers)[idx:idx + (2 ** 31 - 1)])
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return reformatted_mapped_identifiers
+
+    def generates_specific_genomic_identifier_maps(self) -> None:
+        """Method takes a list of information needed to create mappings between specific sets of genomic identifiers.
+
+        Returns:
+            None.
+        """
+
+        print('\t- Generating Genomic Identifier Cross-Mapping Sets')
+
+        self._loads_genomic_typing_dictionary()  # creates genomic typing dictionary
+        reformatted_mapped_identifiers = self.creates_master_genomic_identifier_map()
+        gene_sets = [
+            ['/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt', 'ensembl_gene_id', 'entrez_id', 'gene_type_update', False, False],
+            ['/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt', 'transcript_stable_id', 'pro_id',
+             'transcript_type_update', False, True],
+            ['/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt', 'entrez_id', 'transcript_stable_id', 'transcript_type_update',
+             False, False],
+            ['/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt', 'entrez_id', 'pro_id', 'gene_type_update', False, True],
+            ['/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt', 'symbol', 'transcript_stable_id', 'transcript_type_update',
+             False, False],
+            ['/STRING_PRO_ONTOLOGY_MAP.txt', 'protein_stable_id', 'pro_id', None, False, True],
+            ['/UNIPROT_ACCESSION_PRO_ONTOLOGY_MAP.txt', 'uniprot_id', 'pro_id', None, False, True]
+        ]
+
+        for x in gene_sets:
+            genomic_id_mapper(reformatted_mapped_identifiers,
+                              self.temp_dir + x[0], x[1], x[2], x[3], x[4], x[5])  # type: ignore
+            self.uploads_data_to_gcs_bucket(x[0])  # type: ignore
+
+        return None
+
+    def _processes_mesh_data(self) -> pandas.DataFrame:
+        """Parses MeSH data converting it from n-triples format into Pandas DataFrame that can be merged with ChEBI
+        data.
+
+        Returns:
+            msh_df: A Pandas Data Frame containing three columns: ID (i.e. 'MESH' identifiers), STR (i.e. string
+                labels or synonyms), and TYP (i.e. a string denoting if the STR column entry is a 'NAME' or 'SYNONYM').
+            msh_dict: A nested dict where keys are MeSH identifiers and the values are a dict of labels, dbxrefs, and
+                synonyms for each key.
+        """
+
+        print('\t- Processing MeSH Data')
+
+        mesh = [x.split('> ') for x in tqdm(open(self.downloads_data_from_gcs_bucket('mesh*.nt'), 'r').readlines())]
+        msh_dict: Dict = {}
+        res: List = []
+        for row in tqdm(mesh):
+            s, p, o, dbx, lab, msh_type = row[0].split('/')[-1], row[1].split('#')[-1], row[2], None, None, None
+            if s[0] in ['C', 'D'] and ('.' not in s and 'Q' not in s) and len(s) >= 5:
+                s = 'MESH_' + s
+                if p == 'preferredConcept' or p == 'concept': dbx = 'MESH_' + o.split('/')[-1]
+                if 'label' in p.lower(): lab = o.split('"')[1]
+                if 'type' in p.lower(): msh_type = o.split('#')[1]
+                if s in msh_dict.keys():
+                    if dbx is not None: msh_dict[s]['dbx'].add(dbx)
+                    if lab is not None: msh_dict[s]['lab'].add(lab)
+                    if msh_type is not None: msh_dict[s]['type'].add(msh_type)
+                else:
+                    msh_dict[s] = {'dbx': set() if dbx is None else {dbx}, 'lab': set() if lab is None else {lab},
+                                   'type': set() if msh_type is None else {msh_type}, 'syn': set()}
+        for key in tqdm(msh_dict.keys()):  # fine tune dictionary - obtain labels for each entry's synonym identifiers
+            for i in msh_dict[key]['dbx']:
+                if len(msh_dict[key]['dbx']) > 0 and i in msh_dict.keys(): msh_dict[key]['syn'] |= msh_dict[i]['lab']
+
+        # expand data and convert to pandas DataFrame
+        for key, value in tqdm(msh_dict.items()):
+            res += [[key, list(value['lab'])[0], 'NAME']]
+            if len(value['syn']) > 0:
+                for i in value['syn']:
+                    res += [[key, i, 'SYNONYM']]
+        msh_df = pandas.DataFrame({'ID': [x[0] for x in res], 'TYP': [x[2] for x in res], 'STR': [x[1] for x in res]})
+        msh_df['STR'] = msh_df['STR'].str.lower()
+        msh_df['STR'] = msh_df['STR'].str.replace('[^\w]', '')  # remove white space and punctuation
+
+        return msh_df, msh_dict
+
+    def _processes_chebi_data(self) -> pandas.DataFrame:
+        """Parses ChEBI data into a Pandas DataFrame that can be merged with MeSH data.
+
+        Returns:
+            chebi_df: A Pandas DataFrame containing three columns: ID (i.e. 'CHEBI' identifiers), STR (i.e. string
+                labels or synonyms), and TYP (i.e. a string denoting if the STR column entry is a 'NAME' or 'SYNONYM').
+        """
+
+        print('\t- Processing ChEBI Data')
+
+        chebi = self.reads_gcs_bucket_data_to_df(filename='names.tsv', delm='\t', skip=0, head=0, sht=None)
+        chebi_df = chebi[['COMPOUND_ID', 'TYPE', 'NAME']]  # remove unneeded columns
+        chebi_df.drop_duplicates(subset=None, keep='first', inplace=True)
+        chebi_df.columns = ['ID', 'TYP', 'STR']  # rename columns
+        chebi_df['ID'] = chebi_df['ID'].apply(lambda x: "{}{}".format('CHEBI_', x))  # append CHEBI to # in each id
+        chebi_df['STR'] = chebi_df['STR'].str.lower()
+        chebi_df['STR'] = chebi_df['STR'].str.replace('[^\w]', '')  # remove white space and punctuation
+
+        return chebi_df
+
+    def creates_chebi_to_mesh_identifier_mappings(self) -> None:
+        """Recapitulates the LOOM algorithm (https://www.bioontology.org/wiki/BioPortal_Mappings) utilized by the
+        BioPortal API to create mappings between MeSH and ChEBI identifiers. This is accomplished by performing an
+        inner join on the MeSH and ChEBI Pandas DataFrames. The resulting mappings are then written out locally and
+        pushed to the process_data directory in the Google Cloud Storage bucket for the current build.
+
+        Return:
+            None.
+        """
+
+        print('Creating MeSH-ChEBI Identifier Cross-Mapping Data')
+
+        mesh_df, mesh_dict = self._processes_mesh_data()
+        chebi_df = self._processes_chebi_data()
+        merge_cols = ['STR', 'ID']
+        identifier_merge = pandas.merge(chebi_df[merge_cols], mesh_df[merge_cols], on='STR', how='inner')
+
+        # filter merged data
+        mesh_edges = set()
+        for idx, row in identifier_merge.drop_duplicates().iterrows():
+            mesh, chebi = row['ID_y'], row['ID_x']
+            syns = [x for x in mesh_dict[mesh]['dbx'] if 'C' in x or 'D' in x]
+            mesh_edges.add(tuple([mesh, chebi]))
+            if len(syns) > 0:
+                for x in syns:
+                    mesh_edges.add(tuple([x, chebi]))
+
+        # write results and push data to gcs bucket
+        filename = 'MESH_CHEBI_MAP.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as out:
+            for pair in mesh_edges:
+                out.write(pair[0] + '\t' + pair[1] + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def _preprocess_mondo_mapping_data(self) -> Dict:
+        """Method processes MonDO Disease Ontology (MONDO) ontology data in order to create a dictionary that aligns
+        MONDO concepts with other types of disease terminology identifiers. This is done by obtaining database
+        cross-references (dbxrefs) for each ontology and then combining the results into a single large dictionary
+        keyed by dbxrefs with MONDO and HP identifiers as values.
+
+        Returns:
+            mondo_dict: A dict where disease identifiers mapped to mondo are keys and mondo identifiers are values.
+        """
+
+        print('\t- Loading MonDO Disease Ontology Data')
+
+        mondo_graph = Graph().parse(self.downloads_data_from_gcs_bucket('mondo_with_imports.owl'))
+        dbxref_res = gets_ontology_class_dbxrefs(mondo_graph)[0]
+        mondo_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')}
+                      for k, v in dbxref_res.items()
+                      if 'MONDO' in str(v)}
+
+        return mondo_dict
+
+    def _preprocess_hpo_mapping_data(self) -> Dict:
+        """Method processes Human Phenotype Ontology (HPO) ontology data in order to create a dictionary that aligns HPO
+        concepts with other types of disease terminology identifiers. This is done by obtaining database
+        cross-references (dbxrefs) for each ontology and then combining the results into a single large dictionary
+        keyed by dbxrefs with MONDO and HPO identifiers as values.
+
+        Returns:
+            hp_dict: A dict where disease identifiers mapped to hpo are keys and mondo identifiers are values.
+        """
+
+        print('\t- Loading Human Phenotype Ontology Data')
+
+        hp_graph = Graph().parse(self.downloads_data_from_gcs_bucket('hp_with_imports.owl'))
+        dbxref_res = gets_ontology_class_dbxrefs(hp_graph)[0]
+        hp_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')}
+                   for k, v in dbxref_res.items()
+                   if 'HP' in str(v)}
+
+        return hp_dict
+
+    def creates_disease_identifier_mappings(self) -> None:
+        """Creates Human Phenotype Ontology (HPO) and MonDO Disease Ontology (MONDO) dbxRef maps and then uses them
+        with the DisGEeNET UMLS disease mappings to create a master mapping between all disease identifiers to HPO
+        and MONDO.
+
+        Returns:
+            None.
+        """
+
+        print('Creating Phenotype and Disease Identifier Cross-Mapping Data')
+
+        mondo_dict, hp_dict = self._preprocess_mondo_mapping_data(), self._preprocess_hpo_mapping_data()
+        data = self.reads_gcs_bucket_data_to_df(filename='disease_mappings.tsv', delm='\t', skip=0, head=0, sht=None)
+        data['vocabulary'], data['diseaseId'] = data['vocabulary'].str.lower(), data['diseaseId'].str.lower()
+        data['vocabulary'] = ['doid' if x == 'do' else 'ordoid' if x == 'ordo' else x for x in data['vocabulary']]
+
+        # get all CUIs mapped to HPO and MONDO
+        ont_dict: Dict = {}
+        disease_data_keep = data.query('vocabulary == "hpo" | vocabulary == "mondo"')
+        for idx, row in tqdm(disease_data_keep.iterrows(), total=disease_data_keep.shape[0]):
+            if row['vocabulary'] == 'mondo': key, value = 'umls:' + row['diseaseId'], 'MONDO:' + row['code']
+            else: key, value = 'umls:' + row['diseaseId'], row['code']
+            if key in ont_dict.keys(): ont_dict[key] |= {value}
+            else: ont_dict[key] = {value}
+        for key in tqdm(ont_dict.keys()):  # add ontology mappings from MONDO and HPO
+            if key in mondo_dict.keys(): ont_dict[key] = set(list(ont_dict[key]) + list(mondo_dict[key]))
+            if key in hp_dict.keys(): ont_dict[key] = set(list(ont_dict[key]) + list(hp_dict[key]))
+        # get all rows for HPO/MONDO CUIs to obtain mappings to other disease identifiers
+        disease_dict: Dict = {}
+        disease_data_other = data[data.diseaseId.isin(disease_data_keep['diseaseId'])]
+        for idx, row in tqdm(disease_data_other.iterrows(), total=disease_data_other.shape[0]):
+            vocab, ids, code = row['vocabulary'], row['diseaseId'], row['code']
+            if vocab == 'mondo' or vocab == 'hpo':
+                key, value = 'umls:' + ids.lower(), code
+                if key in disease_dict.keys(): disease_dict[key] |= {value}
+                else: disease_dict[key] = {value}
+            else:
+                if 'mondo' not in code or 'hp' not in code:
+                    if ':' not in code: key, value = vocab + ':' + code, ont_dict['umls:' + ids]
+                    else: key, value = code, ont_dict['umls:' + ids]
+                    if key in disease_dict.keys(): disease_dict[key] |= value
+                    else: disease_dict[key] = value
+
+        # save data and push to GCS bucket
+        file1, file2 = 'DISEASE_MONDO_MAP.txt', 'PHENOTYPE_HPO_MAP.txt'
+        with open(self.temp_dir + '/' + file1, 'w') as out1, open(self.temp_dir + '/' + file2, 'w') as out2:
+            for k, v in tqdm({**disease_dict, **mondo_dict, **hp_dict}.items()):
+                if any(x for x in v if x.startswith('MONDO')):
+                    for idx in [x.replace(':', '_') for x in v if 'MONDO' in x]:
+                        out1.write(k.upper() + '\t' + idx + '\n')
+                if any(x for x in v if x.startswith('HP')):
+                    for idx in [x.replace(':', '_') for x in v if 'HP' in x]:
+                        out2.write(k.upper() + '\t' + idx + '\n')
+        self.uploads_data_to_gcs_bucket(file1)
+        self.uploads_data_to_gcs_bucket(file2)
+
+        return None
+
+    def _hpa_gtex_ontology_alignment(self) -> None:
+        """Processes data to align Human Protein Atlas (HPA) and Genotype-Tissue Expression Project (GTEx) data to the
+        Uber-Anatomy (UBERON), Cell Ontology (CL), and the Cell Line Ontology (CLO). The processed data is then
+        written to a txt file and pushed to GCS.
+
+        Returns:
+            None.
+        """
+
+        data_file, sheet = 'zooma_tissue_cell_mapping_04JAN2020.xlsx', 'Concept_Mapping - 04JAN2020'
+        mapping_data = self.reads_gcs_bucket_data_to_df(filename=data_file, delm='\t', skip=0, head=0, sht=sheet)
+        mapping_data.fillna('NA', inplace=True)
+
+        # write data to useful format for pheknowlator and push to gcs
+        filename = 'HPA_GTEx_TISSUE_CELL_MAP.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as out:
+            for idx, row in tqdm(mapping_data.iterrows(), total=mapping_data.shape[0]):
+                if row['UBERON'] != 'NA': out.write(str(row['TERM']).strip() + '\t' + str(row['UBERON']).strip() + '\n')
+                if row['CL'] != 'NA': out.write(str(row['TERM']).strip() + '\t' + str(row['CL']).strip() + '\n')
+                if row['CLO'] != 'NA': out.write(str(row['TERM']).strip() + '\t' + str(row['CLO']).strip() + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def _extracts_hpa_tissue_information(self) -> pandas.DataFrame:
+        """Method reads in Human Protein Atlas (HPA) data and saves the columns, which contain the anatomical
+        entities (i.e. cell types, cell lines, tissues, and fluids) that need manual alignment to ontologies. These
+        data are not necessarily needed for every build, only when updating the ontology alignment mappings.
+
+        Returns:
+             hpa: A Pandas DataFrame object containing tissue data.
+        """
+
+        hpa = self.reads_gcs_bucket_data_to_df(filename='proteinatlas_search.tsv', delm='\t', skip=0, head=0, sht=None)
+        hpa.fillna('None', inplace=True)
+
+        # write results
+        filename = 'HPA_tissues.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as outfile:
+            for x in tqdm(list(hpa.columns)):
+                if x.endswith('[NX]'): outfile.write(x.split('RNA - ')[-1].split(' [NX]')[:-1][0] + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return hpa
+
+    def processes_hpa_gtex_data(self) -> None:
+        """Method processes and combines gene expression experiment results from the Human protein Atlas (HPA) and the
+        Genotype-Tissue Expression Project (GTEx). Additional details provided below on how each source are processed.
+            - HPA: The HPA data is reformatted so all tissue, cell, cell lines, and fluid types are stored as a nested
+              list. The anatomy type is specified as an item in the list according to its type.
+            - GTEx: All protein-coding genes that appear in the HPA data set are removed. Then, only those non-coding
+              genes with a median expression >= 1.0 are maintained. GTEx data are formatted such the anatomical
+              entities are stored as columns and genes stored as rows, thus the expression filtering step is
+              performed while also reformatting the file, resulting in a nested list.
+
+        Returns:
+            None.
+        """
+
+        print('Creating Human Protein Atlas and Genotype-Tissue Expression Project Cross-Mapping Data')
+
+        hpa = self._extracts_hpa_tissue_information()
+        f_name = 'GTEx_Analysis_*_RNASeQC*_gene_median_tpm.gct'
+        gtex = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=2, head=0, sht=None)
+        gtex.fillna('None', inplace=True)
+        gtex['Name'].replace('(\..*)', '', inplace=True, regex=True)
+
+        # process human protein atlas data
+        hpa_results = []
+        for idx, row in tqdm(hpa.iterrows(), total=hpa.shape[0]):
+            ens, gene, uniprot, evid = str(row['Ensembl']), str(row['Gene']), str(row['Uniprot']), str(row['Evidence'])
+            if row['RNA tissue specific NX'] != 'None':
+                for x in row['RNA tissue specific NX'].split(';'):
+                    hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]
+            if row['RNA cell line specific NX'] != 'None':
+                for x in row['RNA cell line specific NX'].split(';'):
+                    hpa_results += [[ens, gene, uniprot, evid, 'cell line', str(x.split(':')[0])]]
+            if row['RNA brain regional specific NX'] != 'None':
+                for x in row['RNA brain regional specific NX'].split(';'):
+                    hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]
+            if row['RNA blood cell specific NX'] != 'None':
+                for x in row['RNA blood cell specific NX'].split(';'):
+                    hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]
+            if row['RNA blood lineage specific NX'] != 'None':
+                for x in row['RNA blood lineage specific NX'].split(';'):
+                    hpa_results += [[ens, gene, uniprot, evid, 'anatomy', str(x.split(':')[0])]]
+        # process gtex data -- using only those protein-coding genes not already in hpa
+        gtex_results, hpa_genes = [], list(hpa['Ensembl'].drop_duplicates(keep='first', inplace=False))
+        gtex = gtex.loc[gtex['Name'].apply(lambda i: i not in hpa_genes)]
+        for idx, row in tqdm(gtex.iterrows(), total=gtex.shape[0]):
+            for col in list(gtex.columns)[2:]:
+                typ = 'cell line' if 'Cells' in col else 'anatomy'
+                if row[col] >= 1.0:
+                    evidence = 'Evidence at transcript level'
+                    gtex_results += [[str(row['Name']), str(row['Description']), 'None', evidence, typ, str(col)]]
+
+        # write results
+        filename = 'HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as out:
+            for x in hpa_results + gtex_results:
+                out.write(x[0] + '\t' + x[1] + '\t' + x[2] + '\t' + x[3] + '\t' + x[4] + '\t' + x[5] + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def _preprocess_pathway_mapping_data(self) -> Dict:
+        """Method processes the Pathway Ontology (PW) data in order to create a dictionary that aligns PW concepts
+        with other types of pathway identifiers.
+
+        Returns:
+             id_mappings: A dict containing mappings between the PW and other relevant ontologies.
+        """
+
+        print('\t- Loading Protein Ontology Data')
+
+        pw_graph = Graph().parse(self.downloads_data_from_gcs_bucket('pw_with_imports.owl'))
+        dbxref_res = gets_ontology_class_dbxrefs(pw_graph)[0]
+        dbxref_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')}
+                       for k, v in dbxref_res.items() if 'PW_' in str(v)}
+        syn_res = gets_ontology_class_synonyms(pw_graph)[0]
+        synonym_dict = {str(k).lower().split('/')[-1]: {str(v).split('/')[-1].replace('_', ':')}
+                        for k, v in syn_res.items() if 'PW_' in str(v)}
+        id_mappings = {**dbxref_dict, **synonym_dict}
+
+        return id_mappings
+
+    def _processes_reactome_data(self) -> Dict:
+        """Reads in different annotation data sets provided by reactome and combines them into a dictionary.
+
+        Returns:
+            reactome: A dict mapping different pathway identifiers to the Pathway Ontology.
+        """
+
+        print('\t- Loading Reactome Annotation Data')
+
+        r_name, g_name, c_name = 'ReactomePathways.txt', 'gene_association.reactome', 'ChEBI2Reactome_All_Levels.txt'
+
+        # reactome pathways
+        reactome_pathways = self.reads_gcs_bucket_data_to_df(filename=r_name, delm='\t', skip=0, head=None, sht=None)
+        reactome_pathways = reactome_pathways.loc[reactome_pathways[2].apply(lambda x: x == 'Homo sapiens')]
+        reactome = {x: {'PW_0000001'} for x in set(list(reactome_pathways[0]))}
+        # reactome - GO associations
+        reactome_pathways2 = self.reads_gcs_bucket_data_to_df(filename=g_name, delm='\t', skip=1, head=None, sht=None)
+        reactome_pathways2 = reactome_pathways2.loc[reactome_pathways2[12].apply(lambda x: x == 'taxon:9606')]
+        reactome.update({x.split(':')[-1]: {'PW_0000001'} for x in set(list(reactome_pathways2[5]))})
+        # reactome CHEBI
+        reactome_pathways3 = self.reads_gcs_bucket_data_to_df(filename=c_name, delm='\t', skip=0, head=None, sht=None)
+        reactome_pathways3 = reactome_pathways3.loc[reactome_pathways3[5].apply(lambda x: x == 'Homo sapiens')]
+        reactome.update({x: {'PW_0000001'} for x in set(list(reactome_pathways3[1]))})
+
+        return reactome
+
+    def _processes_compath_pathway_data(self, reactome: Dict, pw_dict: Dict) -> Dict:
+        """Processes compath pathway mappings data, extending the input reactome dictionary by extending it to add
+        mappings from KEGG to reactome and the Pathway Ontology (PW).
+
+        Args:
+            reactome: A dict mapping different pathway identifiers to the Pathway Ontology.
+            pw_dict: A dict containing dbxref mappings to PW identifiers.
+
+        Returns:
+             reactome: An extended dict mapping different pathway identifiers to the Pathway Ontology.
+        """
+
+        print('\t- Loading ComPath Canonical Pathway Data')
+
+        f_name = 'compath_canonical_pathway_mappings.txt'
+        compath = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=None, sht=None)
+        compath.fillna('None', inplace=True)
+        for idx, row in tqdm(compath.iterrows(), total=compath.shape[0]):
+            if row[6] == 'kegg' and 'kegg:' + row[5].strip('path:hsa') in pw_dict.keys() and row[2] == 'reactome':
+                for x in pw_dict['kegg:' + row[5].strip('path:hsa')]:
+                    if row[1] in reactome.keys():
+                        reactome[row[1]] |= {x.split('/')[-1]}
+                    else:
+                        reactome[row[1]] = {x.split('/')[-1]}
+            if (row[2] == 'kegg' and 'kegg:' + row[1].strip('path:hsa') in pw_dict.keys()) and row[6] == 'reactome':
+                for x in pw_dict['kegg:' + row[1].strip('path:hsa')]:
+                    if row[5] in reactome.keys():
+                        reactome[row[5]] |= {x.split('/')[-1]}
+                    else:
+                        reactome[row[5]] = {x.split('/')[-1]}
+
+        return reactome
+
+    def _processes_kegg_pathway_data(self, reactome: Dict, pw_dict: Dict) -> Dict:
+        """Processes KEGG-Reactome  data, extending the input reactome dictionary by extending it to add mappings from
+        KEGG to reactome and the Pathway Ontology (PW).
+
+        Args:
+            reactome: A dict mapping different pathway identifiers to the Pathway Ontology.
+            pw_dict: A dict containing dbxref mappings to PW identifiers.
+
+        Returns:
+             reactome: An extended dict mapping different pathway identifiers to the Pathway Ontology.
+        """
+
+        print('\t- Loading KEGG Data')
+
+        f_name = 'kegg_reactome.csv'
+        kegg_reactome_map = self.reads_gcs_bucket_data_to_df(filename=f_name, delm=',', skip=0, head=0, sht=None)
+        src, tar, tar_ids, src_ids = 'Source Resource', 'Target Resource', 'Target ID', 'Source ID'
+
+        for idx, row in tqdm(kegg_reactome_map.iterrows(), total=kegg_reactome_map.shape[0]):
+            if row[src] == 'reactome' and 'kegg:' + row[tar_ids].strip('path:hsa') in pw_dict.keys():
+                for x in pw_dict['kegg:' + row[tar_ids].strip('path:hsa')]:
+                    if row[src_ids] in reactome.keys(): reactome[row[src_ids]] |= {x.split('/')[-1]}
+                    else: reactome[row[src_ids]] = {x.split('/')[-1]}
+            if row[tar] == 'reactome' and 'kegg:' + row[src].strip('path:hsa') in pw_dict.keys():
+                for x in pw_dict['kegg:' + row[src_ids].strip('path:hsa')]:
+                    if row[tar_ids] in reactome.keys(): reactome[row[tar_ids]] |= {x.split('/')[-1]}
+                    else: reactome[row[tar_ids]] = {x.split('/')[-1]}
+
+        return reactome
+
+    @staticmethod
+    def _queries_reactome_api(reactome: Dict) -> Dict:
+        """Runs a set of reactome identifiers against the reactome API in order to obtain mappings to the Gene
+        Ontology, specifically to Biological Processes.
+
+        Args:
+            reactome: A dict mapping different pathway identifiers to the Pathway Ontology.
+
+        Returns:
+             reactome: An extended dict mapping different pathway identifiers to the Pathway Ontology.
+        """
+
+        print('\t- Querying Reactome API to Obtain Reactome-GO Biological Process Mappings')
+
+        for request_ids in tqdm(list(chunks(list(reactome.keys()), 20))):
+            result, key = content.query_ids(ids=','.join(request_ids)), 'goBiologicalProcess'
+            if result is not None:
+                for res in result:
+                    if key in res.keys():
+                        if res['stId'] in reactome.keys(): reactome[res['stId']] |= {'GO_' + res[key]['accession']}
+                        else: reactome[res['stId']] = {'GO_' + res[key]['accession']}
+
+        return reactome
+
+    def _creates_pathway_identifier_mappings(self) -> Dict:
+        """Processes the canonical pathways and other kegg-reactome pathway mapping files from the ComPath
+        Ecosystem in order to create the following identifier mappings: Reactome Pathway Identifiers ➞ KEGG Pathway
+        Identifiers ➞ Pathway Ontology Identifiers.
+
+        Returns:
+            reactome: A dict mapping different types of pathway identifiers to sequence ontology classes.
+        """
+
+        print('\nCreating Pathway Ontology Identifier Cross-Mapping Data')
+
+        pw_dict = self._preprocess_pathway_mapping_data()
+        reactome = self._processes_reactome_data()
+        compath_reactome = self._processes_compath_pathway_data(reactome, pw_dict)
+        kegg_reactome = self._processes_kegg_pathway_data(compath_reactome, pw_dict)
+        reactome = self._queries_reactome_api(kegg_reactome)
+
+        # write data
+        filename = 'REACTOME_PW_GO_MAPPINGS.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as out:
+            for key in tqdm(reactome.keys()):
+                for x in reactome[key]:
+                    if x.startswith('PW') or x.startswith('GO'): out.write(key + '\t' + x + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return reactome
+
+    def _preprocesses_gene_types(self, genomic_map: Dict) -> Dict:
+        """Creates mappings between bio types for different gene identifiers to sequence ontology classes.
+
+        Args:
+            genomic_map: A dict containing mappings between gene identifier types and Sequence Ontology identifiers.
+
+        Returns:
+            sequence_map: A dict containing genomic identifiers as keys and Sequence Ontology classes as values.
+        """
+
+        print('\t- Mapping Sequence Ontology Classes to Gene Identifiers')
+
+        gene_ids = pickle.load(open(self.temp_dir + '/Merged_gene_rna_protein_identifiers.pkl', 'rb'), encoding='bytes')
+        sequence_map: Dict = {}
+        for ids in tqdm(gene_ids.keys()):
+            if ids.startswith('entrez_id_') and ids.replace('entrez_id_', '') != 'None':
+                id_clean = ids.replace('entrez_id_', '')
+                ensembl = [x.replace('ensembl_gene_type_', '') for x in gene_ids[ids] if
+                           x.startswith('ensembl_gene_type') and x != 'ensembl_gene_type_unknown']
+                hgnc = [x.replace('hgnc_gene_type_', '') for x in gene_ids[ids] if
+                        x.startswith('hgnc_gene_type') and x != 'hgnc_gene_type_unknown']
+                entrez = [x.replace('entrez_gene_type_', '') for x in gene_ids[ids] if
+                          x.startswith('entrez_gene_type') and x != 'entrez_gene_type_unknown']
+                # determine gene type
+                if len(ensembl) > 0: gene_type = genomic_map[ensembl[0].replace('ensembl_gene_type_', '') + '_Gene']
+                elif len(hgnc) > 0: gene_type = genomic_map[hgnc[0].replace('hgnc_gene_type_', '') + '_Gene']
+                elif len(entrez) > 0: gene_type = genomic_map[entrez[0].replace('entrez_gene_type_', '') + '_Gene']
+                else: gene_type = 'SO_0000704'
+                # update sequence map
+                if id_clean in sequence_map.keys(): sequence_map[id_clean] += [gene_type]
+                else: sequence_map[id_clean] = [gene_type]
+
+        return sequence_map
+
+    def _preprocesses_transcript_types(self, genomic_map: Dict, sequence_map: Dict) -> Dict:
+        """Creates mappings between bio types for different transcript identifiers to sequence ontology classes.
+
+        Args:
+            genomic_map: A dict containing mappings between transcript identifier types and Sequence Ontology classes.
+            sequence_map: A dict containing genomic identifiers as keys and Sequence Ontology classes as values.
+
+        Returns:
+            sequence_map: A dict containing genomic identifiers as keys and Sequence Ontology classes as values.
+        """
+
+        print('\t- Mapping Sequence Ontology Classes to Transcript Identifiers')
+
+        trans: Dict = {}
+        trans_id: str = 'transcript_stable_id'
+        f_name = self.temp_dir + '/ensembl_identifier_data_cleaned.txt'
+        trans_data = pandas.read_csv(f_name, header=0, delimiter='\t', low_memory=False)
+        for idx, row in tqdm(trans_data.iterrows(), total=trans_data.shape[0]):
+            if row[trans_id] != 'None':
+                if row[trans_id].replace('transcript_stable_id_', '') in trans.keys():
+                    trans[row[trans_id].replace('transcript_stable_id_', '')] += [row['ensembl_transcript_type']]
+                else:
+                    trans[row[trans_id].replace('transcript_stable_id_', '')] = [row['ensembl_transcript_type']]
+
+        # update SO map dictionary
+        for ids in tqdm(trans.keys()):
+            if trans[ids][0] == 'protein_coding': trans_type = genomic_map['protein-coding_Transcript']
+            elif trans[ids][0] == 'misc_RNA': trans_type = genomic_map['miscRNA_Transcript']
+            else: trans_type = genomic_map[list(set(trans[ids]))[0] + '_Transcript']
+            sequence_map[ids] = [trans_type, 'SO_0000673']
+
+        return sequence_map
+
+    def _preprocesses_variant_types(self, genomic_map: Dict, sequence_map: Dict) -> Dict:
+        """Creates mappings between bio types for different transcript identifiers to sequence ontology classes.
+
+        Args:
+            genomic_map: A dict containing mappings between variant identifier types and Sequence Ontology identifiers.
+             sequence_map: A dict containing genomic identifiers as keys and Sequence Ontology classes as values.
+
+        Returns:
+            sequence_map: A dict containing genomic identifiers as keys and Sequence Ontology classes as values.
+        """
+
+        print('\t- Mapping Sequence Ontology Classes to Variant Identifiers')
+
+        f_name: str = 'variant_summary.txt'
+        v_df: Dict = {}
+        variant_data = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=0, sht=None)
+        for idx, row in tqdm(variant_data.iterrows(), total=variant_data.shape[0]):
+            if row['Assembly'] == 'GRCh38' and row['RS# (dbSNP)'] != -1:
+                if 'rs' + str(row['RS# (dbSNP)']) in v_df.keys(): v_df['rs' + str(row['RS# (dbSNP)'])] |= {row['Type']}
+                else: v_df['rs' + str(row['RS# (dbSNP)'])] = {row['Type']}
+        # update SO map dictionary
+        for identifier in tqdm(v_df.keys()):
+            for typ in v_df[identifier]:
+                var_type = genomic_map[typ.lower() + '_Variant']
+                if identifier in sequence_map.keys(): sequence_map[identifier] += [var_type]
+                else: sequence_map[identifier] = [var_type]
+
+        return sequence_map
+
+    def _creates_sequence_identifier_mappings(self) -> Dict:
+        """Maps a Sequence Ontology concept to all gene, transcript, and variant identifiers.
+
+        Returns:
+            sequence_map: A dict containing different types of genomic identifiers as keys and Sequence Ontology
+                classes as values.
+        """
+
+        print('\nCreating Sequence Ontology Identifier Cross-Mapping Data')
+
+        f_name, sht = 'genomic_sequence_ontology_mappings.xlsx', 'GenomicType_SO_Map_09Mar2020'
+        mapping_data = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=0, sht=sht)
+        genomic_type_so_map = {}
+        for idx, row in tqdm(mapping_data.iterrows(), total=mapping_data.shape[0]):
+            genomic_type_so_map[row['source_*_type'] + '_' + row['Genomic']] = row['SO ID']
+
+        # add genes, transcripts, and variants
+        genomic_sequence_map = self._preprocesses_gene_types(genomic_type_so_map)
+        trans_sequence_map = self._preprocesses_transcript_types(genomic_type_so_map, genomic_sequence_map)
+        sequence_map = self._preprocesses_variant_types(genomic_type_so_map, trans_sequence_map)
+
+        # output data
+        filename = 'SO_GENE_TRANSCRIPT_VARIANT_TYPE_MAPPING.txt'
+        with open(self.temp_dir + '/' + filename, 'w') as outfile:
+            for key in tqdm(sequence_map.keys()):
+                for map_type in sequence_map[key]:
+                    outfile.write(key + '\t' + map_type + '\n')
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return sequence_map
+
+    def combines_pathway_and_sequence_ontology_dictionaries(self) -> None:
+        """Combines the Pathway Ontology and Sequence Ontology dictionaries into a dict. This data is needed for the
+        subclass construction approach of the knowledge graph build process.
+
+        Returns:
+            None.
+        """
+
+        print('Creating Pathway and Sequence Ontology Mapping Dictionary')
+
+        sequence_map = self._creates_sequence_identifier_mappings()
+        reactome_map = self._creates_pathway_identifier_mappings()
+
+        # combine genomic and pathway maps + iterate over pathway lists and combine them into a single dictionary
+        sequence_map.update(reactome_map)
+        subclass_mapping = {}
+        for key in tqdm(sequence_map.keys()):
+            subclass_mapping[key] = sequence_map[key]
+
+        # save file and push to gcs bucket
+        filename = 'subclass_construction_map.pkl'
+        pickle.dump(subclass_mapping, open(self.temp_dir + '/' + filename, 'wb'), protocol=4)
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def _processes_protein_ontology_data(self) -> networkx.MultiDiGraph:
+        """Reads in the PRotein Ontology (PR) into an RDFLib graph object and converts it to a Networkx MultiDiGraph
+        object.
+
+        Returns:
+            networkx_mdg: A Networkx MultiDiGraph object containing protein ontology data.
+        """
+
+        print('\t- Loading Protein Ontology Data')
+
+        pr_graph = Graph().parse(self.downloads_data_from_gcs_bucket('pr_with_imports.owl'))
+
+        # convert rdf graph to networkx multidigraph
+        networkx_mdg: networkx.MultiDiGraph = networkx.MultiDiGraph()
+        for s, p, o in tqdm(pr_graph):
+            networkx_mdg.add_edge(s, o, **{'key': p})
+
+        return networkx_mdg
+
+    def _logically_verifies_human_protein_ontology(self, input_filename, output_filename, reasoner) -> None:
+        """Logically verifies constructed Human Protein Ontology by running a deductive logic reasoner.
+
+        Args:
+            input_filename: A string containing the name of the file to run the reasoner on.
+            output_filename: A string containing the filename to write the reasoner results to.
+            reasoner: A string containing the name of the deductive reasoner to use.
+
+        Returns:
+            None.
+        """
+
+        print('\t- Logically Verifying Constructed Human Protein Ontology')
+
+        # run reasoner
+        command = "./pkt_kg/libs/owltools ./{} --reasoner {} --run-reasoner --assert-implied -o ./{}"
+        return_code = os.system(command.format(input_filename, reasoner.lower(), output_filename))
+
+        if return_code == 0:
+            ontology_file_formatter(self.temp_dir, '/' + input_filename.split('/')[-1], self.owltools_location)
+            ontology_file_formatter(self.temp_dir, '/' + output_filename.split('/')[-1], self.owltools_location)
+            self.uploads_data_to_gcs_bucket(input_filename.split('/')[-1])
+            self.uploads_data_to_gcs_bucket(output_filename.split('/')[-1])
+        else:
+            raise ValueError('Reasoner Finished with Errors.')
+
+        return None
+
+    def constructs_human_protein_ontology(self) -> None:
+        """Creates a human version of the PRotein Ontology (PRO) by traversing the ontology to obtain forward and
+        reverse breadth first search. If the resulting human PRO contains a single connected component it's written
+        locally.
+
+        Returns:
+            None.
+
+        Raises:
+            ValueError: When the human versions of the PRO contains more than a single connected component.
+        """
+
+        print('Constructing a Human Version of the PRotein Ontology')
+
+        # download needed data
+        networkx_mdg = self._processes_protein_ontology_data()
+        df_list = pandas.read_html(self.downloads_data_from_gcs_bucket('human_pro_classes.html'))
+        human_pro_classes = list(df_list[-1]['PRO_term'])
+
+        # create a new graph using breadth first search paths
+        human_pro_graph, human_networkx_mdg = Graph(), networkx.MultiDiGraph()
+        for node in tqdm(human_pro_classes):
+            forward = list(networkx.edge_bfs(networkx_mdg, URIRef(node), orientation='original'))
+            reverse = list(networkx.edge_bfs(networkx_mdg, URIRef(node), orientation='reverse'))
+            # add edges from forward and reverse breadth first search paths
+            for path in forward + reverse:
+                human_pro_graph.add((path[0], path[2], path[1]))
+                human_networkx_mdg.add_edge(path[0], path[1], path[2])
+
+        # check data and write it locally
+        if networkx.number_connected_components(human_networkx_mdg.to_undirected()) == 1:
+            human_pro_graph.serialize(destination=self.temp_dir + '/human_pro.owl', format='xml')
+            f_name1, f_name2 = self.temp_dir + '/human_pro.owl', self.temp_dir + '/pr_with_imports.owl'
+            self._logically_verifies_human_protein_ontology(f_name1, f_name2, 'elk')
+        else:
+            raise ValueError('Human PRO Contains More than a Single Connected Component')
+
+        return None
+
+    def processes_relation_ontology_data(self) -> None:
+        """Processes the Relations Ontology (RO) in order to obtain all ObjectProperties and their inverse relations.
+        Additionally, the method writes out a file of all of the labels for all relations.
+
+        Returns:
+             None.
+        """
+
+        print('Creating Required Relations Ontology Data')
+
+        ro_graph = Graph().parse(self.downloads_data_from_gcs_bucket('ro_with_imports.owl'))
+        labs = {str(x[2]).lower(): str(x[0]) for x in ro_graph if '/RO_' in str(x[0]) and 'label' in str(x[1]).lower()}
+
+        # identify relations and their inverses
+        write_dir, filename1 = './resources/construction_approach/', 'INVERSE_RELATIONS.txt'
+        with open(self.temp_dir + '/' + filename1, 'w') as out1:
+            out1.write('Relation' + '\t' + 'Inverse_Relation' + '\n')
+            for s, p, o in ro_graph:
+                if 'owl#inverseOf' in str(p) and ('RO' in str(s) and 'RO' in str(o)):
+                    out1.write(str(s.split('/')[-1]) + '\t' + str(o.split('/')[-1]) + '\n')
+                    out1.write(str(o.split('/')[-1]) + '\t' + str(s.split('/')[-1]) + '\n')
+        self.uploads_data_to_gcs_bucket(filename1)
+
+        # identify relation labels
+        filename2 = 'RELATIONS_LABELS.txt'
+        with open(self.temp_dir + '/' + filename2, 'w') as out1:
+            out1.write('Relation' + '\t' + 'Label' + '\n')
+            for k, v in labs.items():
+                out1.write(str(k).split('/')[-1] + '\t' + str(v) + '\n')
+        self.uploads_data_to_gcs_bucket(filename2)
+
+        return None
+
+    def processes_clinvar_data(self) -> None:
+        """Processes ClinVar data by performing light tidying and filtering and then outputs data needed to create
+        mappings between genes, variants, and phenotypes.
+
+        Returns:
+            None.
+        """
+
+        print('Generating ClinVar Cross-Mapping Data')
+
+        f_name = 'variant_summary.txt'
+        clinvar_data = self.reads_gcs_bucket_data_to_df(filename=f_name, delm='\t', skip=0, head=0, sht=None)
+        clinvar_data.fillna('None', inplace=True)
+
+        # explode nested data
+        explode_df_clinvar = explodes_data(clinvar_data.copy(), ['PhenotypeIDS'], ';')
+        explode_df_clinvar = explodes_data(explode_df_clinvar.copy(), ['PhenotypeIDS'], ',')
+        explode_df_clinvar['PhenotypeIDS'].replace('Orphanet:ORPHA', 'ORPHA:', inplace=True, regex=True)
+        explode_df_clinvar['PhenotypeIDS'].replace('Human Phenotype Ontology:HP:', 'HP_', inplace=True, regex=True)
+
+        # write data
+        filename = 'CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt'
+        explode_df_clinvar.to_csv(self.temp_dir + '/' + filename, header=True, sep='\t', encoding='utf-8', index=False)
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def processes_cofactor_catalyst_data(self) -> None:
+        """Processes uniprot-cofactor-catalyst.tab file from the Uniprot Knowledge Base in order to enable the building
+        of protein-cofactor and protein-catalyst edges.
+
+        Returns:
+            None.
+        """
+
+        print('Creating Protein-Cofactor and Protein-Catalyst Cross-Mapping Data')
+
+        data = open(self.downloads_data_from_gcs_bucket('uniprot-cofactor-catalyst.tab')).readlines()
+
+        # reformat data and write data
+        filename1, filename2 = 'UNIPROT_PROTEIN_COFACTOR.txt', 'UNIPROT_PROTEIN_CATALYST.txt'
+        with open(self.temp_dir + '/' + filename1, 'w') as out1, open(self.temp_dir + '/' + filename2, 'w') as out2:
+            for line in tqdm(data):
+                if 'CHEBI' in line.split('\t')[4]:  # cofactors
+                    for i in line.split('\t')[4].split(';'):
+                        chebi = i.split('[')[-1].replace(']', '').replace(':', '_')
+                        out1.write('PR_' + line.split('\t')[3].strip(';') + '\t' + chebi + '\n')
+                if 'CHEBI' in line.split('\t')[5]:  # catalysts
+                    for j in line.split('\t')[5].split(';'):
+                        chebi = j.split('[')[-1].replace(']', '').replace(':', '_')
+                        out2.write('PR_' + line.split('\t')[3].strip(';') + '\t' + chebi + '\n')
+        # push data to gsc bucket
+        self.uploads_data_to_gcs_bucket(filename1)
+        self.uploads_data_to_gcs_bucket(filename2)
+
+        return None
+
+    def _creates_gene_metadata_dict(self) -> Dict:
+        """Creates a dictionary to store labels, synonyms, and a description for each Entrez gene identifier present
+        in the input data file.
+
+        Returns:
+            gene_metadata_dict: A dict containing metadata that's keyed by Entrez gene identifier and whose values are
+                dicts containing label, description, and synonym information. For example:
+                    {{'http://www.ncbi.nlm.nih.gov/gene/1': {
+                        'Label': 'A1BG',
+                        'Description': "A1BG is 'protein-coding' and is located on chromosome 19 (19q13.43).",
+                        'Synonym': 'HEL-S-163pA|A1B|ABG|HYST2477alpha-1B-glycoprotein|GAB'}, ...}
+        """
+
+        print('\t- Generating Metadata for Gene Identifiers')
+
+        f_name = self.temp_dir + '/' + 'Homo_sapiens.gene_info'
+        data = pandas.read_csv(f_name, header=0, delimiter='\t', low_memory=False)
+        data = data.loc[data['#tax_id'].apply(lambda x: x == 9606)]
+        data.fillna('None', inplace=True)
+        data.replace('-', 'None', inplace=True, regex=False)
+
+        # create metadata
+        genes, lab, desc, syn = [], [], [], []
+        for idx, row in tqdm(data.iterrows(), total=data.shape[0]):
+            gene_id, sym, defn, gene_type = row['GeneID'], row['Symbol'], row['description'], row['type_of_gene']
+            chrom, map_loc, s1, s2 = row['chromosome'], row['map_location'], row['Synonyms'], row['Other_designations']
+            if gene_id != 'None':
+                genes.append('http://www.ncbi.nlm.nih.gov/gene/' + str(gene_id))
+                if sym != 'None' or sym != '':
+                    lab.append(sym)
+                else:
+                    lab.append('Entrez_ID:' + gene_id)
+                if 'None' not in [defn, gene_type, chrom, map_loc]:
+                    desc_str = "{} has locus group '{}' and is located on chromosome {} ({})."
+                    desc.append(desc_str.format(sym, gene_type, chrom, map_loc))
+                else:
+                    desc.append("{} locus group '{}'.".format(sym, gene_type))
+                if s1 != 'None' and s2 != 'None':
+                    syn.append('|'.join(set([x for x in (s1 + s2).split('|') if x != 'None' or x != ''])))
+                elif s1 != 'None':
+                    syn.append('|'.join(set([x for x in s1.split('|') if x != 'None' or x != ''])))
+                elif s2 != 'None':
+                    syn.append('|'.join(set([x for x in s2.split('|') if x != 'None' or x != ''])))
+                else:
+                    syn.append('None')
+
+        # combine into new data frame then convert it to dictionary
+        metadata = pandas.DataFrame(list(zip(genes, lab, desc, syn)), columns=['ID', 'Label', 'Description', 'Synonym'])
+        metadata = metadata.astype(str)
+        metadata.drop_duplicates(subset='ID', keep='first', inplace=True)
+        metadata.set_index('ID', inplace=True)
+        gene_metadata_dict = metadata.to_dict('index')
+
+        return gene_metadata_dict
+
+    def _creates_transcript_metadata_dict(self) -> Dict:
+        """Creates a dictionary to store labels, synonyms, and a description for each Entrez gene identifier present
+        in the input data file.
+
+        Returns:
+            rna_metadata_dict: A dict containing metadata that's keyed by Ensembl transcript identifier and whose values
+                are a dict containing label, description, and synonym information. For example:
+                    {'https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=ENST00000456328': {
+                        'Label': 'DDX11L1-202',
+                        'Description': "Transcript DDX11L1-202 is classified as type 'processed_transcript'.",
+                        'Synonym': 'None'}, ...}
+        """
+
+        print('\t- Generating Metadata for Transcript Identifiers')
+
+        f_name = 'ensembl_identifier_data_cleaned.txt'
+        dup_cols = ['transcript_stable_id', 'transcript_name', 'ensembl_transcript_type']
+        data = pandas.read_csv(self.temp_dir + '/' + f_name, header=0, delimiter='\t', low_memory=False)
+        data = data.loc[data['transcript_stable_id'].apply(lambda x: x != 'None')]
+        data.drop(['ensembl_gene_id', 'symbol', 'protein_stable_id', 'uniprot_id', 'master_transcript_type',
+                   'entrez_id', 'ensembl_gene_type', 'master_gene_type', 'symbol'], axis=1, inplace=True)
+        data.drop_duplicates(subset=dup_cols, keep='first', inplace=True)
+        data.fillna('None', inplace=True)
+
+        # create metadata
+        rna, lab, desc, syn = [], [], [], []
+        for idx, row in tqdm(data.iterrows(), total=data.shape[0]):
+            rna_id, ent_type, nme = row[dup_cols[0]], row[dup_cols[2]], row[dup_cols[1]]
+            rna.append('https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=' + rna_id)
+            if nme != 'None':
+                lab.append(nme)
+            else:
+                lab.append('Ensembl_Transcript_ID:' + rna_id)
+                nme = 'Ensembl_Transcript_ID:' + rna_id
+            if ent_type != 'None': desc.append("Transcript {} is classified as type '{}'.".format(nme, ent_type))
+            else: desc.append('None')
+            syn.append('None')
+
+        # combine into new data frame then convert it to dictionary
+        metadata = pandas.DataFrame(list(zip(rna, lab, desc, syn)), columns=['ID', 'Label', 'Description', 'Synonym'])
+        metadata = metadata.astype(str)
+        metadata.drop_duplicates(subset='ID', keep='first', inplace=True)
+        metadata.set_index('ID', inplace=True)
+        rna_metadata_dict = metadata.to_dict('index')
+
+        return rna_metadata_dict
+
+    def _creates_variant_metadata_dict(self) -> Dict:
+        """Creates a dictionary to store labels, synonyms, and a description for each ClinVar variant identifier present
+        in the input data file.
+
+        Returns:
+            variant_metadata_dict: A dict containing metadata that's keyed by ClinVar variant identifier and whose
+                values are a dict containing label, description, and synonym information. For example:
+                    {{'https://www.ncbi.nlm.nih.gov/snp/rs141138948': {
+                        'Label': 'NM_016042.4(EXOSC3):c.395A>C (p.Asp132Ala)',
+                        'Description': "This variant is a germline single nucleotide variant on chromosome 9
+                        (NC_000009.12, start:37783993/stop:37783993 positions,cytogenetic location:9p13.2) and
+                        has clinical significance 'Pathogenic/Likely pathogenic'. This entry is for the GRCh38 and was
+                        last reviewed on Sep 30, 2020 with review status 'criteria provided, multiple submitters,
+                        no conflict'.", 'Synonym': 'None'}, ...}
+        """
+
+        print('\t- Generating Metadata for Variant Identifiers')
+
+        f_name = self.temp_dir + '/' + 'variant_summary.txt'
+        data = pandas.read_csv(f_name, header=0, delimiter='\t', low_memory=False)
+        data = data.loc[data['Assembly'].apply(lambda x: x == 'GRCh38')]
+        data = data.loc[data['RS# (dbSNP)'].apply(lambda x: x != -1)]
+        data = data[['#AlleleID', 'Type', 'Name', 'ClinicalSignificance', 'RS# (dbSNP)', 'Origin', 'Start', 'Stop',
+                     'ChromosomeAccession', 'Chromosome', 'ReferenceAllele', 'Assembly', 'AlternateAllele',
+                     'Cytogenetic', 'ReviewStatus', 'LastEvaluated']]
+        data.replace('na', 'None', inplace=True)
+        data.fillna('None', inplace=True)
+        data.sort_values('LastEvaluated', ascending=False, inplace=True)
+        data.drop_duplicates(subset='RS# (dbSNP)', keep='first', inplace=True)
+
+        # create metadata
+        var, label, desc, syn = [], [], [], []
+        for idx, row in tqdm(data.iterrows(), total=data.shape[0]):
+            var_id, lab = row['RS# (dbSNP)'], row['Name']
+            if var_id != 'None':
+                var.append('https://www.ncbi.nlm.nih.gov/snp/rs' + str(var_id))
+                if lab != 'None': label.append(lab)
+                else: label.append('dbSNP_ID:rs' + str(var_id))
+                sent = "This variant is a {} {} located on chromosome {} ({}, start:{}/stop:{} positions, " + \
+                       "cytogenetic location:{}) and has clinical significance '{}'. " + \
+                       "This entry is for the {} and was last reviewed on {} with review status '{}'."
+                desc.append(
+                    sent.format(row['Origin'].replace(';', '/'), row['Type'].replace(';', '/'), row['Chromosome'],
+                                row['ChromosomeAccession'], row['Start'], row['Stop'], row['Cytogenetic'],
+                                row['ClinicalSignificance'], row['Assembly'], row['LastEvaluated'],
+                                row['ReviewStatus']).replace('None', 'UNKNOWN'))
+                syn.append('None')
+
+        # combine into new data frame then convert it to dictionary
+        metadata = pandas.DataFrame(list(zip(var, label, desc, syn)), columns=['ID', 'Label', 'Description', 'Synonym'])
+        metadata.drop_duplicates(subset=None, keep='first', inplace=True)
+        metadata = metadata.astype(str)
+        metadata.set_index('ID', inplace=True)
+        variant_metadata_dict = metadata.to_dict('index')
+
+        return variant_metadata_dict
+
+    def _creates_pathway_metadata_dict(self) -> Dict:
+        """Creates a dictionary to store labels, synonyms, and a description for each Reactome Pathway identifier
+        present in the input data file.
+
+        Returns:
+            pathway_metadata_dict: A dict containing metadata that's keyed by Reactome Pathway identifier and whose
+                values are a dict containing label, description, and synonym information. For example:
+                    {{'https://reactome.org/content/detail/R-HSA-157858': {
+                        'Label': 'Gap junction trafficking and regulation',
+                        'Description': 'None',
+                        'Synonym': 'Gap junction trafficking and regulation'}}, ...}
+        """
+
+        print('\t- Generating Metadata for Pathway Identifiers')
+
+        # get metadata then convert it to a dictionary
+        f_name = self.temp_dir + '/' + 'ReactomePathways.txt'
+        data = pandas.read_csv(f_name, header=None, delimiter='\t', low_memory=False)
+        data = data.loc[data[2].apply(lambda x: x == 'Homo sapiens')]
+        nodes = set(list(data[0]))
+        metadata = metadata_api_mapper(list(nodes))
+        metadata['ID'] = metadata['ID'].map('https://reactome.org/content/detail/{}'.format)
+        metadata.set_index('ID', inplace=True)
+        pathway_metadata_dict = metadata.to_dict('index')
+
+        return pathway_metadata_dict
+
+    def _creates_relations_metadata_dict(self) -> Dict:
+        """Creates a dictionary to store labels, synonyms, and a description for each Relation Ontology identifier
+        present in the input data file.
+
+        Returns:
+            relation_metadata_dict: A dict containing metadata that's keyed by Relations identifier and whose
+                values are a dict containing label, description, and synonym information. For example:
+                    {{'http://purl.obolibrary.org/obo/RO_0002310': {
+                    'Label': 'exposure event or process',
+                    'Description': 'A process occurring within or in the vicinity of an organism that exerts some causal
+                    influence on the organism via the interaction between an exposure stimulus and an exposure receptor.
+                    The exposure stimulus may be a process, material entity or condition (e.g. lack of nutrients).
+                    The exposure receptor can be an organism, organism population or a part of an organism.',
+                    'Synonym': 'None'}}, ...}
+        """
+
+        # get ontology information
+        ro_graph = Graph().parse(self.downloads_data_from_gcs_bucket('ro_with_imports.owl'))
+        relation_metadata_dict, obo = {}, Namespace('http://purl.obolibrary.org/obo/')
+        cls = [x for x in gets_ontology_classes(ro_graph) if '/RO_' in str(x)] + \
+              [x for x in gets_object_properties(ro_graph) if '/RO_' in str(x)]
+        master_synonyms = [x for x in ro_graph if 'synonym' in str(x[1]).lower() and isinstance(x[0], URIRef)]
+
+        for x in tqdm(cls):
+            cls_label = list(ro_graph.objects(x, RDFS.label))
+            labels = str(cls_label[0]) if len(cls_label) > 0 else 'None'
+            cls_syn = [str(i[2]) for i in master_synonyms if x == i[0]]
+            synonym = str(cls_syn[0]) if len(cls_syn) > 0 else 'None'
+            cls_desc = list(ro_graph.objects(x, obo.IAO_0000115))
+            desc = '|'.join([str(cls_desc[0])]) if len(cls_desc) > 0 else 'None'
+            relation_metadata_dict[str(x)] = {'Label': labels, 'Description': desc, 'Synonym': synonym}
+
+        return relation_metadata_dict
+
+    def creates_non_ontology_class_metadata_dict(self) -> None:
+        """Combines the gene metadata, transcript metadata, variant metadata, pathway metadata, and relations metadata
+        dictionaries into a single large metadata dictionary. See the specific functions used below for examples of
+        the output.
+
+        Returns:
+            None.
+        """
+
+        print('Creating Master Metadata Dictionary for Non-Ontology Entities')
+
+        # create single dictionary of
+        master_metadata_dictionary = {**self._creates_gene_metadata_dict(),
+                                      **self._creates_transcript_metadata_dict(),
+                                      **self._creates_variant_metadata_dict(),
+                                      **self._creates_pathway_metadata_dict(),
+                                      **self._creates_relations_metadata_dict()}
+
+        # save data and push to gcs bucket
+        filename = 'node_metadata_dict.pkl'
+        pickle.dump(master_metadata_dictionary, open(self.temp_dir + '/' + filename, 'wb'), protocol=4)
+        self.uploads_data_to_gcs_bucket(filename)
+
+        return None
+
+    def preprocesses_build_data(self) -> None:
+        """Master method that performs all needed data preprocessing tasks in preparation of generating PheKnowLator
+        knowledge graphs. This method completes this work in 10 steps.
+
+        Returns:
+            None.
+        """
+
+        print('*** PROCESSING LINKED OPEN DATA SOURCES ***')
+
+        # STEP 1: Human Transcript, Gene, and Protein Identifier Mapping
+        print('\nSTEP 1: HUMAN TRANSCRIPT, GENE, PROTEIN IDENTIFIER MAPPING')
+        self.generates_specific_genomic_identifier_maps()
+
+        # STEP 2: MeSH-ChEBI Identifier Mapping
+        print('\STEP 2: MESH-CHEBI IDENTIFIER MAPPING')
+        self.creates_chebi_to_mesh_identifier_mappings()
+
+        # STEP 3: Disease and Phenotype Identifier Mapping
+        print('\nSTEP 3: DISEASE-PHENOTYPE IDENTIFIER MAPPING')
+        self.creates_disease_identifier_mappings()
+
+        # STEP 4: Human Protein Atlas/GTEx Tissue/Cells Edge Data
+        print('\nSTEP 4: CREATING HPA + GTEX IDENTIFIER EDGE DATA')
+        self._hpa_gtex_ontology_alignment()
+        self.processes_hpa_gtex_data()
+
+        # STEP 5: Creating Pathway and Sequence Ontology Mappings
+        print('\nSTEP 5: PATHWAY + SEQUENCE ONTOLOGY IDENTIFIER MAPPING')
+        self.combines_pathway_and_sequence_ontology_dictionaries()
+
+        # STEP 6: Creating a Human Protein Ontology
+        print('\nSTEP 6: CREATING A HUMAN PROTEIN ONTOLOGY')
+        self.constructs_human_protein_ontology()
+
+        # STEP 7: Extracting Relations Ontology Information
+        print('\nSTEP 7: EXTRACTING RELATION ONTOLOGY INFORMATION')
+        self.processes_relation_ontology_data()
+
+        # STEP 8: Clinvar Variant-Diseases and Phenotypes Edge Data
+        print('\nSTEP 8: CREATING CLINVAR VARIANT, DISEASE, PHENOTYPE DATA')
+        self.processes_clinvar_data()
+
+        # STEP 9: Uniprot Protein-Cofactor and Protein-Catalyst Edge Data
+        print('\nSTEP 9: CREATING COFACTOR + CATALYST EDGE DATA')
+        self.processes_cofactor_catalyst_data()
+
+        # STEP 10: Non-Ontology Metadata Dictionary
+        print('\nSTEP 10: CREATING ONO-ONTOLOGY METADATA DICTIONARY')
+        self.creates_non_ontology_class_metadata_dict()
+
+        return None
diff --git a/builds/data_to_download.txt b/builds/data_to_download.txt
new file mode 100644
index 000000000..78d03e45d
--- /dev/null
+++ b/builds/data_to_download.txt
@@ -0,0 +1,63 @@
+# ontology data
+http://purl.obolibrary.org/obo/hp.owl
+http://purl.obolibrary.org/obo/go.owl
+http://purl.obolibrary.org/obo/mondo.owl
+http://purl.obolibrary.org/obo/vo.owl
+http://purl.obolibrary.org/obo/chebi.owl
+http://purl.obolibrary.org/obo/uberon/ext.owl
+http://purl.obolibrary.org/obo/clo.owl
+http://purl.obolibrary.org/obo/pr.owl
+http://purl.obolibrary.org/obo/so.owl
+http://purl.obolibrary.org/obo/pw.owl
+http://purl.obolibrary.org/obo/ro.owl
+
+# linked open data
+# human transcript, gene, and protein identifier mapping
+hgnc_complete_set.txt, ftp://ftp.ebi.ac.uk/pub/databases/genenames/new/tsv/hgnc_complete_set.txt
+Homo_sapiens.GRCh38.102.gtf, ftp://ftp.ensembl.org/pub/release-102/gtf/homo_sapiens/Homo_sapiens.GRCh38.102.gtf.gz
+Homo_sapiens.GRCh38.102.uniprot.tsv, ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.uniprot.tsv.gz
+Homo_sapiens.GRCh38.102.entrez.tsv, ftp://ftp.ensembl.org/pub/release-102/tsv/homo_sapiens/Homo_sapiens.GRCh38.102.entrez.tsv.gz
+uniprot_identifier_mapping.tab, https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Creviewed%2Cdatabase(GeneID)%2Cdatabase(Ensembl)%2Cdatabase(HGNC)%2Cgenes(ALTERNATIVE)%2Cgenes(PREFERRED)&format=tab
+Homo_sapiens.gene_info, ftp://ftp.ncbi.nih.gov/gene/DATA/GENE_INFO/Mammalia/Homo_sapiens.gene_info.gz
+promapping.txt, https://proconsortium.org/download/current/promapping.txt
+mesh2021.nt, ftp://nlmpubs.nlm.nih.gov/online/mesh/rdf/2021/mesh2021.nt
+names.tsv, ftp://ftp.ebi.ac.uk/pub/databases/chebi/Flat_file_tab_delimited/names.tsv.gz
+# disease and phenotype identifiers
+disease_mappings.tsv, https://www.disgenet.org/static/disgenet_ap1/files/downloads/disease_mappings.tsv.gz
+# human protein atlas/gtex tissue/cells - uberon + cell ontology + cell line ontology
+proteinatlas_search.tsv.gz, https://www.proteinatlas.org/api/search_download.php?search=&columns=g,eg,up,pe,rnatsm,rnaclsm,rnacasm,rnabrsm,rnabcsm,rnablsm,scl,t_RNA_adipose_tissue,t_RNA_adrenal_gland,t_RNA_amygdala,t_RNA_appendix,t_RNA_basal_ganglia,t_RNA_bone_marrow,t_RNA_breast,t_RNA_cerebellum,t_RNA_cerebral_cortex,t_RNA_cervix,_uterine,t_RNA_colon,t_RNA_corpus_callosum,t_RNA_ductus_deferens,t_RNA_duodenum,t_RNA_endometrium_1,t_RNA_epididymis,t_RNA_esophagus,t_RNA_fallopian_tube,t_RNA_gallbladder,t_RNA_heart_muscle,t_RNA_hippocampal_formation,t_RNA_hypothalamus,t_RNA_kidney,t_RNA_liver,t_RNA_lung,t_RNA_lymph_node,t_RNA_midbrain,t_RNA_olfactory_region,t_RNA_ovary,t_RNA_pancreas,t_RNA_parathyroid_gland,t_RNA_pituitary_gland,t_RNA_placenta,t_RNA_pons_and_medulla,t_RNA_prostate,t_RNA_rectum,t_RNA_retina,t_RNA_salivary_gland,t_RNA_seminal_vesicle,t_RNA_skeletal_muscle,t_RNA_skin_1,t_RNA_small_intestine,t_RNA_smooth_muscle,t_RNA_spinal_cord,t_RNA_spleen,t_RNA_stomach_1,t_RNA_testis,t_RNA_thalamus,t_RNA_thymus,t_RNA_thyroid_gland,t_RNA_tongue,t_RNA_tonsil,t_RNA_urinary_bladder,t_RNA_vagina,t_RNA_B-cells,t_RNA_dendritic_cells,t_RNA_granulocytes,t_RNA_monocytes,t_RNA_NK-cells,t_RNA_T-cells,t_RNA_total_PBMC,cell_RNA_A-431,cell_RNA_A549,cell_RNA_AF22,cell_RNA_AN3-CA,cell_RNA_ASC_diff,cell_RNA_ASC_TERT1,cell_RNA_BEWO,cell_RNA_BJ,cell_RNA_BJ_hTERT+,cell_RNA_BJ_hTERT+_SV40_Large_T+,cell_RNA_BJ_hTERT+_SV40_Large_T+_RasG12V,cell_RNA_CACO-2,cell_RNA_CAPAN-2,cell_RNA_Daudi,cell_RNA_EFO-21,cell_RNA_fHDF/TERT166,cell_RNA_HaCaT,cell_RNA_HAP1,cell_RNA_HBEC3-KT,cell_RNA_HBF_TERT88,cell_RNA_HDLM-2,cell_RNA_HEK_293,cell_RNA_HEL,cell_RNA_HeLa,cell_RNA_Hep_G2,cell_RNA_HHSteC,cell_RNA_HL-60,cell_RNA_HMC-1,cell_RNA_HSkMC,cell_RNA_hTCEpi,cell_RNA_hTEC/SVTERT24-B,cell_RNA_hTERT-HME1,cell_RNA_HUVEC_TERT2,cell_RNA_K-562,cell_RNA_Karpas-707,cell_RNA_LHCN-M2,cell_RNA_MCF7,cell_RNA_MOLT-4,cell_RNA_NB-4,cell_RNA_NTERA-2,cell_RNA_PC-3,cell_RNA_REH,cell_RNA_RH-30,cell_RNA_RPMI-8226,cell_RNA_RPTEC_TERT1,cell_RNA_RT4,cell_RNA_SCLC-21H,cell_RNA_SH-SY5Y,cell_RNA_SiHa,cell_RNA_SK-BR-3,cell_RNA_SK-MEL-30,cell_RNA_T-47d,cell_RNA_THP-1,cell_RNA_TIME,cell_RNA_U-138_MG,cell_RNA_U-2_OS,cell_RNA_U-2197,cell_RNA_U-251_MG,cell_RNA_U-266/70,cell_RNA_U-266/84,cell_RNA_U-698,cell_RNA_U-87_MG,cell_RNA_U-937,cell_RNA_WM-115,blood_RNA_basophil,blood_RNA_classical_monocyte,blood_RNA_eosinophil,blood_RNA_gdT-cell,blood_RNA_intermediate_monocyte,blood_RNA_MAIT_T-cell,blood_RNA_memory_B-cell,blood_RNA_memory_CD4_T-cell,blood_RNA_memory_CD8_T-cell,blood_RNA_myeloid_DC,blood_RNA_naive_B-cell,blood_RNA_naive_CD4_T-cell,blood_RNA_naive_CD8_T-cell,blood_RNA_neutrophil,blood_RNA_NK-cell,blood_RNA_non-classical_monocyte,blood_RNA_plasmacytoid_DC,blood_RNA_T-reg,blood_RNA_total_PBMC,brain_RNA_amygdala,brain_RNA_basal_ganglia,brain_RNA_cerebellum,brain_RNA_cerebral_cortex,brain_RNA_hippocampal_formation,brain_RNA_hypothalamus,brain_RNA_midbrain,brain_RNA_olfactory_region,brain_RNA_pons_and_medulla,brain_RNA_thalamus&format=tsv
+GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct, https://storage.googleapis.com/gtex_analysis_v8/rna_seq_data/GTEx_Analysis_2017-06-05_v8_RNASeQCv1.1.9_gene_median_tpm.gct.gz
+zooma_tissue_cell_mapping_04JAN2020.xlsx, https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/zooma_tissue_cell_mapping_04JAN2020.xlsx
+# mapping reactome pathways to the pathway ontology
+ReactomePathways.txt, https://reactome.org/download/current/ReactomePathways.txt
+gene_association.reactome, https://reactome.org/download/current/gene_association.reactome.gz
+ChEBI2Reactome_All_Levels.txt, https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt
+compath_canonical_pathway_mappings.txt, http://compath.scai.fraunhofer.de/export_mappings
+kegg_reactome.csv, https://raw.githubusercontent.com/ComPath/resources/master/mappings/kegg_reactome.csv
+# mapping genomic identifiers to the sequence ontology
+genomic_sequence_ontology_mappings.xlsx, https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/genomic_sequence_ontology_mappings.xlsx
+# protein ontology consortium sparql query results
+human_pro_classes.html, https://sparql.proconsortium.org/virtuoso/sparql?query=PREFIX+obo%3A+%3Chttp%3A%2F%2Fpurl.obolibrary.org%2Fobo%2F%3E%0D%0A%0D%0ASELECT+%3FPRO_term%0D%0AFROM+%3Chttp%3A%2F%2Fpurl.obolibrary.org%2Fobo%2Fpr%3E%0D%0AWHERE+%7B%0D%0A+++++++%3FPRO_term+rdf%3Atype+owl%3AClass+.%0D%0A+++++++%3FPRO_term+rdfs%3AsubClassOf+%3Frestriction+.%0D%0A+++++++%3Frestriction+owl%3AonProperty+obo%3ARO_0002160+.%0D%0A+++++++%3Frestriction+owl%3AsomeValuesFrom+obo%3ANCBITaxon_9606+.%0D%0A%0D%0A+++++++%23+use+this+to+filter-out+things+like+hgnc+ids%0D%0A+++++++FILTER+%28regex%28%3FPRO_term%2C%22http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2F*%22%29%29+.%0D%0A%7D&format=text%2Fhtml&debug=
+# clinvar variant-diseases and phenotypes
+variant_summary.txt, ftp://ftp.ncbi.nlm.nih.gov/pub/clinvar/tab_delimited/variant_summary.txt.gz
+# uniprot protein-cofactor and protein-catalyst
+uniprot-cofactor-catalyst.tab, https://www.uniprot.org/uniprot/?query=&fil=organism%3A%22Homo%20sapiens%20(Human)%20%5B9606%5D%22&columns=id%2Creviewed%2Centry%20name%2Cdatabase(PRO)%2Cchebi(Cofactor)%2Cchebi(Catalytic%20activity)&format=tab
+
+# edge source list
+CTD_chemicals_diseases.tsv, http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
+CTD_chem_gene_ixns.tsv, http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
+CTD_chem_go_enriched.tsv, http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
+ChEBI2Reactome_All_Levels.txt, https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt
+CTD_chemicals_diseases.tsv, http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
+phenotype_annotation.tab, http://compbio.charite.de/jenkins/job/hpo.annotations/lastStableBuild/artifact/misc/phenotype_annotation.tab
+curated_gene_disease_associations.tsv, https://www.disgenet.org/static/disgenet_ap1/files/downloads/curated_gene_disease_associations.tsv.gz
+COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt, http://genemania.org/data/current/Homo_sapiens.COMBINED/COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
+CTD_genes_pathways.tsv, http://ctdbase.org/reports/CTD_genes_pathways.tsv.gz
+gene_association.reactome, https://reactome.org/download/current/gene_association.reactome.gz
+goa_human.gaf, http://geneontology.org/gene-associations/goa_human.gaf.gz
+UniProt2Reactome_All_Levels.txt, https://reactome.org/download/current/UniProt2Reactome_All_Levels.txt
+9606.protein.links.v11.0.txt, https://stringdb-static.org/download/protein.links.v11.0/9606.protein.links.v11.0.txt.gz
+
+# local build data
+OWL_NETS_Property_Types.txt, https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/OWL_NETS_Property_Types.txt
+genomic_typing_dict.pkl, https://storage.googleapis.com/pheknowlator/release_v2.0.0/curated_data/genomic_typing_dict.pkl
\ No newline at end of file
diff --git a/builds/ontology_cleaning.py b/builds/ontology_cleaning.py
new file mode 100644
index 000000000..69558b8f6
--- /dev/null
+++ b/builds/ontology_cleaning.py
@@ -0,0 +1,508 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# import needed libraries
+import datetime
+import fnmatch
+import glob
+import pickle
+import os
+import re
+
+from google.cloud import storage  # type: ignore
+from owlready2 import *  # type: ignore
+from rdflib import BNode, Graph, Literal, Namespace, URIRef  # type: ignore
+from rdflib.namespace import OWL, RDF, RDFS  # type: ignore
+from tqdm import tqdm  # type: ignore
+from typing import Dict, List, Optional, Union
+
+# import script containing helper functions
+from pkt_kg.utils import *
+
+# set global namespace
+schema = Namespace('http://www.w3.org/2001/XMLSchema#')
+obo = Namespace('http://purl.obolibrary.org/obo/')
+oboinowl = Namespace('http://www.geneontology.org/formats/oboInOwl#')
+
+
+class OntologyCleaner(object):
+    """Class provides a container for the ontology cleaning methods, original housed in the Ontology_Cleaning.ipynb
+    Jupyter Notebook. See notebook (https://github.com/callahantiff/PheKnowLator/blob/master/Ontology_Cleaning.ipynb)
+    for more detailed descriptions of each error check performed and how they are resolved.
+
+    Attributes:
+        gcs_bucket: A storage Bucket object specifying a Google Cloud Storage bucket.
+        org_data: A string specifying the location of the original_data directory for a specific build.
+        proc_data: A string specifying the location of the original_data directory for a specific build.
+        temp: A string specifying a temporary directory to use while processing data locally.
+    """
+
+    def __init__(self, gcs_bucket: Union[storage.bucket.Bucket, str], org_data: str, proc_data: str, temp: str) -> None:
+
+        # GOOGLE CLOUD STORAGE VARIABLES
+        self.bucket: Union[storage.bucket.Bucket, str] = gcs_bucket
+        self.original_data: str = org_data
+        self.processed_data: str = proc_data
+        # SETTING LOCAL VARIABLES
+        self.owltools_location = './pkt_kg/libs/owltools'
+        self.temp_dir = temp
+        self.merged_ontology_filename: str = 'PheKnowLator_MergedOntologies.owl'
+        # ONTOLOGY INFORMATION DICTIONARY
+        if isinstance(self.bucket, storage.bucket.Bucket):
+            self.onts = [x.name.split('/')[-1] for x in self.bucket.list_blobs(prefix=self.original_data)
+                         if x.name.endswith('.owl')] + [self.merged_ontology_filename]
+        else: self.onts = glob.glob(self.temp_dir + '*/*.owl')
+        self.ontology_info: Dict = {x: {} for x in self.onts}
+        # OTHER CLASS ATTRIBUTES
+        self.ont_file_location: str = ''
+        self.ont_graph: Graph = Graph()
+        self.ontology_classes: List = []
+        # GENE IDENTIFIER DATA
+        # keep list of withdrawn hgnc gene ids in HPO that need updating
+        self.withdrawn_genes = {  # keep list of withdrawn hgnc gene ids in HPO that need updating
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=21508': ['653067', '653220'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=23418': ['653450'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=18372': ['653067', '653220'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26418': ['132332'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=30679': ['653067', '653220'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26844': ['5414']
+        }
+        f_data = self.temp_dir + '/Merged_gene_rna_protein_identifiers.pkl'
+        if not os.path.exists(f_data):
+            url = 'https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/'
+            data_downloader(url + f_data.split('/')[-1], self.temp_dir + '/')
+            self.gene_ids = pickle.load(open(f_data, 'rb'), encoding='bytes')
+        else:
+            self.gene_ids = pickle.load(open(f_data, 'rb'), encoding='bytes')
+
+    def uploads_data_to_gcs_bucket(self, file_loc: str) -> None:
+        """Takes a file name and pushes the corresponding data referenced by the filename object from a local
+        temporary directory to a Google Cloud Storage bucket.
+
+        Args:
+            file_loc: A string containing the name of file to write to a Google Cloud Storage bucket.
+
+        Returns:
+            None.
+        """
+
+        if isinstance(self.bucket, storage.bucket.Bucket):
+            blob = self.bucket.blob(self.processed_data + file_loc)
+            blob.upload_from_filename(self.temp_dir + '/' + file_loc)
+
+        return None
+
+    def downloads_data_from_gcs_bucket(self, filename: str) -> Optional[str]:
+        """Takes a filename and and downloads the corresponding data to a local temporary directory, if it has not
+        already been downloaded.
+
+        Args:
+            filename: A string containing the name of file to write to a Google Cloud Storage bucket.
+
+        Returns:
+            data_file: A string containing the local filepath for a file downloaded from a GSC bucket.
+
+        Raises:
+            ValueError: when trying to download a non-existent file from the GCS original_data dir of the current build.
+        """
+
+        if isinstance(self.bucket, storage.bucket.Bucket):
+            try:
+                _files = [_.name for _ in self.bucket.list_blobs(prefix=self.original_data)]
+                match_file = fnmatch.filter(_files, '*/' + filename)[0]  # poor man's glob of bucket file directory
+                data_file = self.temp_dir + '/' + match_file.split('/')[-1]
+                if not os.path.exists(data_file):  # only download if file has not yet been downloaded
+                    self.bucket.blob(match_file).download_to_filename(self.temp_dir + '/' + match_file.split('/')[-1])
+            except IndexError:
+                raise ValueError('{} Not in the GCS original_data Directory of the Current Build'.format(filename))
+
+            return data_file
+        else: return None
+
+    def reads_gcs_bucket_data_to_graph(self, file_location: str) -> Graph:
+        """Reads data corresponding to the input file_location variable into a Pandas DataFrame.
+
+        Args:
+            file_location: A string containing the name of file that exists in a Google Cloud Storage bucket.
+
+        Returns:
+             graph: An RDFLib graph object containing data read from a Google Cloud Storage bucket.
+        """
+
+        dat = self.downloads_data_from_gcs_bucket(file_location)
+        graph = Graph().parse(dat, format='xml')
+
+        return graph
+
+    def _logically_verifies_cleaned_ontologies(self) -> None:
+        """Logically verifies an ontology by running the ELK deductive logic reasoner. Before running the reasoner
+        the instantiated RDFLib object is saved locally.
+
+        Returns:
+            None.
+        """
+
+        print('Logically Verifying Ontology')
+
+        # save graph in order to run reasoner
+        filename = self.temp_dir + '/' + self.ont_file_location
+        self.ont_graph.serialize(destination=filename, format='xml')
+
+        command = "./pkt_kg/libs/owltools {} --reasoner {} --run-reasoner --assert-implied -o {}"
+        return_code = os.system(command.format(filename, 'elk', filename))
+        if return_code == 0:
+            ontology_file_formatter(self.temp_dir, '/' + self.ont_file_location, self.owltools_location)
+            self.uploads_data_to_gcs_bucket(self.ont_file_location)
+        else: raise ValueError('Reasoner Finished with Errors.')
+
+        return None
+
+    def updates_ontology_reporter(self) -> None:
+        """Initializes the ontology_info dictionary which tracks ontology changes throughout the cleaning process.
+
+        Returns:
+            None.
+        """
+
+        print('Obtaining Ontology Statistics')
+
+        gcs_org_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.original_data)
+        gcs_proc_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.processed_data)
+        if 'Starting Statistics' not in self.ontology_info[self.ont_file_location].keys():
+            self.ontology_info[self.ont_file_location] = {}
+            self.ontology_info[self.ont_file_location]['Original GCS URL'] = gcs_org_url + self.ont_file_location
+            self.ontology_info[self.ont_file_location]['Processed GCS URL'] = gcs_proc_url + self.ont_file_location
+            key = 'Starting Statistics'
+        else: key = 'Final Statistics'
+        classes, obj_props = len(gets_ontology_classes(self.ont_graph)), len(gets_object_properties(self.ont_graph))
+        triples, indv = len(self.ont_graph), len(list(self.ont_graph.triples((None, RDF.type, OWL.NamedIndividual))))
+        stats = '{} Classes; {} Object Properties; {} Triples; {} Individuals'.format(classes, obj_props, triples, indv)
+        self.ontology_info[self.ont_file_location][key] = stats
+
+        return None
+
+    def _finds_ontology_errors(self) -> Dict:
+        """Catches different types of errors when attempting to load an ontology using the owlready2 library.
+
+        Returns:
+             errors: A dictionary keyed by error type with associated error message stored as the value.
+        """
+
+        errors = {}
+        try: _ = get_ontology(self.temp_dir + '/' + self.ont_file_location).load()  # type: ignore
+        except OwlReadyOntologyParsingError as o: errors['OwlReadyOntologyParsingError'] = str(o)  # type: ignore
+        except KeyError as k: errors['KeyError'] = str(k)
+        except TypeError as p: errors['PunningError'] = str(p)
+
+        return errors
+
+    def fixes_ontology_parsing_errors(self):
+        """Fixes parsing errors detected when attempting to load the ontology. Currently, this method only fixes
+        value errors (i.e. string-typed entities mis-typed as ints) because these are the only types of errors
+        relevant to the set of ontologies utilized in the v2.0.0 PheKnowLator release. It can be easily extended in the
+        future to accommodate other error types.
+
+        Returns:
+            None.
+        """
+
+        print('Finding Parsing Errors')
+
+        errors, error_key, key = self._finds_ontology_errors(), 'OwlReadyOntologyParsingError', self.ont_file_location
+        if error_key in errors.keys():
+            line_num = int(re.findall(r'(?<=line\s).*(?=,)', str(errors[error_key]))[0]) - 1  # get error line
+            raw_data = open(self.temp_dir + '/' + key).readlines()  # read raw data
+            bad_content = re.findall(r'(?<=>).*(?=<)', raw_data[line_num])[0]
+            bad_triple = [x for x in self.ont_graph if bad_content in str(x[0]) or bad_content in str(x[2])]
+            for e in bad_triple:  # repair bad triple -- assuming for now the errors are miss-typed string errors
+                self.ont_graph.add((e[0], e[1], Literal(str(e[2]), datatype=schema.string)))
+                self.ont_graph.remove(e)
+
+            self.ontology_info[key]['ValueErrors'] = errors[error_key]  # update ontology information dictionary
+
+        return None
+
+    def fixes_identifier_errors(self) -> None:
+        """Identifies identifier prefix errors (e.g. "PRO" for the Protein Ontology, which should be "PR"). This is a
+        tricky task to do in an automated manner and is something that should be updated if any new ontologies are
+        added to the PheKnowLator build. Currently it checks and logs any hits, but only fixes the following known
+        errors: Vaccine Ontology: "PRO" which should be "PR".
+
+        Returns:
+            None.
+        """
+
+        print('Fixing Identifier Errors')
+
+        known_errors = ['PRO', 'PR']  # known errors
+        kg_classes, bad_cls, key = gets_ontology_classes(self.ont_graph), set(), self.ont_file_location
+        class_list = [res for res in kg_classes if isinstance(res, URIRef) and 'obo/' in str(res)]
+        all_cls = set([x.split('/')[-1].split('_')[0] for x in class_list])
+        errors = [x for x in all_cls if any(i for i in all_cls if i != x and (i in x or x in i))]
+        self.ontology_info[key]['IdentifierErrors'] = 'Possible ' + '-'.join(errors) if len(errors) > 0 else 'None'
+        for edge in self.ont_graph:
+            if 'http://purl.obolibrary.org/obo/{}_'.format(known_errors[0]) in str(edge[0]):
+                updated_subj = str(edge[0]).replace(known_errors[0], known_errors[1])
+                self.ont_graph.add((URIRef(updated_subj), edge[1], edge[2]))
+                self.ont_graph.remove(edge)
+                bad_cls.add(str(edge[0]))
+            if 'http://purl.obolibrary.org/obo/{}_'.format(known_errors[0]) in str(edge[2]):
+                updated_obj = str(edge[2]).replace(known_errors[0], known_errors[1])
+                self.ont_graph.add((URIRef(updated_obj), edge[1], edge[2]))
+                self.ont_graph.remove(edge)
+                bad_cls.add(str(edge[2]))
+
+            self.ontology_info[key]['IdentifierErrors'] = ', '.join(list(bad_cls)) if len(bad_cls) > 0 else 'None'
+
+        return None
+
+    def removes_deprecated_obsolete_entities(self) -> None:
+        """Identifies and removes all deprecated and obsolete classes.
+
+        Returns:
+            None.
+        """
+
+        print('Removing Deprecated and Obsolete Classes')
+
+        ont, key, schma = self.ont_file_location.split('/')[-1].split('_')[0], self.ont_file_location, schema.boolean
+
+        dep_cls = [x[0] for x in tqdm(list(self.ont_graph.triples((None, OWL.deprecated,
+                                                                   Literal('true', datatype=schma)))))]
+        dep_triples = [x for x in tqdm(self.ont_graph)
+                       if 'deprecated' in ', '.join([str(x[0]).lower(), str(x[1]).lower(), str(x[2]).lower()])
+                       and len(list(self.ont_graph.triples((x[0], RDF.type, OWL.Class)))) == 1]
+        deprecated_classes = set(dep_cls + [x[0] for x in dep_triples])
+
+        obs_cls = [x[0] for x in tqdm(list(self.ont_graph.triples((None, RDFS.subClassOf, oboinowl.ObsoleteClass))))]
+        obs_triples = [x for x in tqdm(self.ont_graph)
+                       if 'obsolete' in ', '.join([str(x[0]).lower(), str(x[1]).lower(), str(x[2]).lower()])
+                       and len(list(self.ont_graph.triples((x[0], RDF.type, OWL.Class)))) == 1 and '#' not in str(x[0])]
+        obsolete_classes = set(obs_cls + [x[0] for x in obs_triples])
+
+        for node in list(deprecated_classes) + list(obsolete_classes):
+            self.ont_graph.remove((node, None, None))
+
+        self.ontology_info[key]['Deprecated'] = len(deprecated_classes) if len(deprecated_classes) > 0 else 'None'
+        self.ontology_info[key]['Obsolete'] = len(obsolete_classes) if len(obsolete_classes) > 0 else 'None'
+
+        return None
+
+    def fixes_punning_errors(self) -> None:
+        """Identifies and resolves three types of punning or entity redeclaration errors:
+            (1) Entities typed as an owl:Class and owl:ObjectProperty --> removes owl:ObjectProperty
+            (2) Entities typed as an owl:Class and owl:NamedIndividual --> removes owl:NamedIndividual
+            (3) Entities typed as an OWL:ObjectProperty and owl:AnnotationProperty --> removes owl:AnnotationProperty
+            (4) owl:ObjectProperties that are rdfs:subProperty of an owl:AnnotationProperty --> remove rdfs:subProperty
+
+        NOTE. Even though a run will yield a clean a result, reading back in the ontology using any OWL API-based
+        tools (e.g. OWL Tools) will result in a single punning error for RO_0002161.
+
+        Returns:
+             None.
+        """
+
+        print('Resolving Punning Errors')
+
+        key, bad_cls, bad_obj, bad_oth = self.ont_file_location, set(), set(), []
+        onts_entities = set([x[0] for x in tqdm(self.ont_graph)])
+        for x in tqdm(onts_entities):
+            ent_types = list(self.ont_graph.triples((x, RDF.type, None)))
+            subpro = list(self.ont_graph.triples((x, RDFS.subPropertyOf, None)))
+            if len(ent_types) > 1:
+                if not any([x for x in ent_types if 'owl' not in str(x[2])]):
+                    # class + object properties --> remove object properties
+                    class_prop, obj_prop = (x, RDF.type, OWL.Class), (x, RDF.type, OWL.ObjectProperty)
+                    if (class_prop in ent_types and obj_prop in ent_types) and str(x) not in bad_cls:
+                        self.ont_graph.remove(obj_prop)
+                        bad_cls.add(str(x))
+                    # class + individual --> remove individual
+                    class_prop, ind_prop = (x, RDF.type, OWL.Class), (x, RDF.type, OWL.NamedIndividual)
+                    if (class_prop in ent_types and ind_prop in ent_types) and str(x) not in bad_cls:
+                        self.ont_graph.remove(ind_prop)
+                        bad_cls.add(str(x))
+                    # object property + annotation property --> remove annotation property
+                    obj_prop, a_prop = (x, RDF.type, OWL.ObjectProperty), (x, RDF.type, OWL.AnnotationProperty)
+                    if (obj_prop in ent_types and a_prop in ent_types) and str(x) not in bad_obj:
+                        self.ont_graph.remove(a_prop)
+                        bad_obj.add(str(x))
+                        # object properties that are subproperty of something that is not an object property
+                        if len(subpro) > 0 and any([x for x in ent_types if OWL.ObjectProperty == x[2]]):
+                            for s in subpro:
+                                for sp in list(self.ont_graph.triples((s[2], RDF.type, None))):
+                                    if OWL.ObjectProperty != sp[2]:
+                                        self.ont_graph.remove(sp)
+                                        bad_obj.add(str(x))
+                    else: bad_oth += [str(ent_types[0]) + '-' + '/'.join([str(x[2]) for x in ent_types])]
+
+        self.ontology_info[key]['PunningErrors - Classes'] = ', '.join(bad_cls) if len(bad_cls) > 0 else 'None'
+        self.ontology_info[key]['PunningErrors - ObjectProperty'] = ', '.join(bad_obj) if len(bad_obj) > 0 else 'None'
+        self.ontology_info[key]['PunningErrors - Other'] = ', '.join(bad_oth) if len(bad_oth) > 0 else 'None'
+
+        return None
+
+    def normalizes_duplicate_classes(self) -> None:
+        """Makes sure that all classes representing the same entity are connected to each other. For example, the
+        Sequence Ontology, ChEBI, PRotein Ontology all include terms for protein, but none of these classes are
+        connected to each other. The follow classes occur in all of the ontologies used in the current build and have
+        to be normalized so that there are not multiple versions of the same concept:
+            - Gene: VO OGG_0000000002. Make the OGG class a subclass of SO gene (SO_0000704)
+            - Protein: SO_0000104, PR_000000001, CHEBI_36080. Make CHEBI and PR a subclass of SO protein (SO_0000104)
+            - Disorder: VO OGMS_0000045. Make OGMS a subclass of MONDO disease (MONDO_0000001)
+            - Antigen: VO OBI_1110034. Make OBI a subclass of CHEBI antigen (CHEBI_59132)
+            - Gelatin: VO_0003030. Make the VO a subclass of CHEBI gelatin (CHEBI_5291)
+            - Hormone: VO FMA_12278. Make FMA a subclass of CHEBI hormone (CHEBI_24621)
+
+        Returns:
+             None.
+        """
+
+        print('Normalizing Duplicate Concepts')
+
+        self.ont_graph.add((obo.OGG_0000000002, RDFS.subClassOf, obo.SO_0000704))  # fix gene class inconsistencies
+        self.ont_graph.add((obo.PR_000000001, RDFS.subClassOf, obo.SO_0000104))  # fix protein class inconsistencies
+        self.ont_graph.add((obo.CHEBI_36080, RDFS.subClassOf, obo.SO_0000104))  # fix protein class inconsistencies
+        self.ont_graph.add((obo.OGMS_0000045, RDFS.subClassOf, obo.MONDO_0000001))  # fix disorder class inconsistencies
+        self.ont_graph.add((obo.OBI_1110034, RDFS.subClassOf, obo.CHEBI_59132))  # fix antigen class inconsistencies
+        self.ont_graph.add((obo.VO_0003030, RDFS.subClassOf, obo.CHEBI_5291))  # fix gelatin class inconsistencies
+        self.ont_graph.add((obo.FMA_12278, RDFS.subClassOf, obo.CHEBI_24621))  # fix hormone class inconsistencies
+        # update ontology information dictionary
+        results = ['OGG_0000000002 rdfs:subClassOf SO_0000704', 'PR_000000001 rdfs:subClassOf SO_0000104',
+                   'CHEBI_36080 rdfs:subClassOf SO_0000104', 'OGMS_0000045 rdfs:subClassOf MONDO_0000001',
+                   'OBI_1110034 rdfs:subClassOf CHEBI_59132', 'VO_0003030 rdfs:subClassOf CHEBI_5291',
+                   'FMA_12278 rdfs:subClassOf CHEBI_24621']
+        self.ontology_info[self.ont_file_location]['Normalized - Duplicates'] = ', '.join(results)
+
+        return None
+
+    def normalizes_existing_classes(self) -> None:
+        """Checks for inconsistencies in ontology classes that overlap with non-ontology entity identifiers (e.g. if
+        HP includes HGNC identifiers, but PheKnowLator utilizes Entrez gene identifiers). While there are other types of
+        identifiers, we focus primarily on resolving the genomic types, since we have a master dictionary we can used to
+        help with this (Merged_gene_rna_protein_identifiers.pkl). This can be updated in future iterations to include
+        other types of identifiers, but given our detailed examination of the v2.0.0 ontologies, these were the
+        identifier types that needed repair.
+
+        Returns:
+            None.
+        """
+
+        print('Normalizing Existing Classes')
+
+        non_ont = set([x for x in gets_ontology_classes(self.ont_graph) if not str(x).startswith(str(obo))])
+        hgnc, url, g = set([x for x in non_ont if 'hgnc' in str(x)]), 'http://www.ncbi.nlm.nih.gov/gene/', self.gene_ids
+        for node in tqdm(hgnc):
+            trips = list(self.ont_graph.triples((node, None, None))) + list(self.ont_graph.triples((None, None, node)))
+            nd = 'hgnc_id_' + str(node).split('/')[-1].split('=')[-1]
+            if nd in g.keys(): ents = [URIRef(url + x) for x in g[nd] if x.startswith('entrez_id_')]
+            else: ents = [URIRef(url + x) for x in self.withdrawn_genes[str(node)]]
+            for edge in trips:
+                if node in edge[0]:
+                    if isinstance(edge[2], URIRef) or isinstance(edge[2], BNode):
+                        for i in ents:
+                            self.ont_graph.add((i, edge[1], edge[2]))
+                            self.ont_graph.remove(edge)
+                    else: self.ont_graph.remove(edge)
+                if node in edge[2]:
+                    for i in ents:
+                        self.ont_graph.add((edge[0], edge[1], i))
+                        self.ont_graph.remove(edge)
+
+        no_ont = len(non_ont)-len(hgnc)
+        self.ontology_info[self.ont_file_location]['Normalized - NonOnt'] = no_ont if no_ont != 0 else 'None'
+        self.ontology_info[self.ont_file_location]['Normalized - Gene IDs'] = len(hgnc) if len(hgnc) > 0 else 'None'
+
+        return None
+
+    def generates_ontology_report(self) -> None:
+        """Parses the ontology_info dictionary in order to create a final ontology report summarizing the cleaning
+        results performed on each ontology and the statistics on the final merged set of ontologies.
+
+        Returns:
+             None.
+        """
+
+        ontology_report_filename = 'ontology_cleaning_report.txt'
+        ont_order = sorted([x for x in self.ontology_info.keys() if not x.startswith('Phe')]) + [self.ont_file_location]
+        with open(self.temp_dir + '/' + ontology_report_filename, 'w') as o:
+            o.write('=' * 50 + '\n{}'.format('ONTOLOGY CLEANING REPORT'))
+            o.write('\n{}\n'.format(str(datetime.datetime.utcnow().strftime('%a %b %d %X UTC %Y'))) + '=' * 50 + '\n\n')
+            for key in ont_order:
+                o.write('\nONTOLOGY: {}\n'.format(key))
+                x = self.ontology_info[key]
+                if 'Original GCS URL' in x.keys(): o.write('\t- Original GCS URL: {}\n'.format(x['Original GCS URL']))
+                if 'Processed GCS URL' in x: o.write('\t- Processed GCS URL: {}\n'.format(x['Processed GCS URL']))
+                o.write('\t- Statistics:\n\t\t- Before Cleaning: {}\n'.format(x['Starting Statistics']))
+                if 'Final Statistics' in x.keys(): o.write('\t\t- After Cleaning: {}\n'.format(x['Final Statistics']))
+                if 'ValueErrors' in x.keys(): o.write('\t- Value Errors: {}\n'.format(x['ValueErrors']))
+                if 'IdentifierErrors' in x.keys(): o.write('\t- Identifier Errors: {}\n'.format(x['IdentifierErrors']))
+                if 'Deprecated' in x.keys(): o.write('\t- Deprecated Classes: {}\n'.format(x['Deprecated']))
+                if 'Obsolete' in x.keys(): o.write('\t- Obsolete Classes: {}\n'.format(x['Obsolete']))
+                o.write('\t- Punning Error:\n\t\t- Classes:\n')
+                if x['PunningErrors - Classes'] != 'None':
+                    for i in x['PunningErrors - Classes'].split(', '): o.write('\t\t\t- {}\n'.format(i))
+                else: o.write('\t\t\t- {}\n'.format(x['PunningErrors - Classes']))
+                o.write('\t\t- ObjectProperties:\n')
+                if x['PunningErrors - ObjectProperty'] != 'None':
+                    for i in x['PunningErrors - ObjectProperty'].split(', '): o.write('\t\t\t- {}\n'.format(i))
+                else: o.write('\t\t\t- {}\n'.format(x['PunningErrors - ObjectProperty']))
+                if 'Normalized - Duplicates' in x.keys():
+                    o.write('\t- Entity Normalization:\n')
+                    if x['Normalized - Duplicates'] != 'None':
+                        for i in x['Normalized - Duplicates'].split(', '): o.write('\t\t- {}\n'.format(i))
+                    else: o.write('\t\t- {}\n'.format(x['Normalized - Duplicates']))
+                    o.write('\t\t- Other Classes that May Need Normalization: {}\n'.format(x['Normalized - NonOnt']))
+                    o.write('\t\t- Normalized HGNC IDs: {}\n'.format(x['Normalized - Gene IDs']))
+
+        self.uploads_data_to_gcs_bucket(ontology_report_filename)
+
+        return None
+
+    def cleans_ontology_data(self) -> None:
+        """Performs all needed ontology cleaning tasks by resolving different types of ontology cleaning steps at the
+        individual ontology- and the merged ontology-level, each are described below:
+            - Individual Ontologies: (1) Parsing Errors, (2) Identifier Errors, (3) Deprecated/Obsolete Errors, and (4)
+              Punning Errors.
+            - Merged Ontologies: (1) Identifier Error, (2) Normalizes Duplicate and Existing Concepts, and (3) Punning
+              Errors.
+
+        Returns:
+            None.
+        """
+
+        print('*** CLEANING ONTOLOGY DATA SOURCES ***')
+
+        for ont in self.ontology_info.keys():
+            if ont != self.merged_ontology_filename:
+                print('\nProcessing Ontology: {}'.format(ont.upper()))
+                self.ont_file_location, self.ont_graph = ont, self.reads_gcs_bucket_data_to_graph(ont)
+                self.updates_ontology_reporter()  # get starting statistics
+                self.fixes_ontology_parsing_errors()
+                self.fixes_identifier_errors()
+                self.removes_deprecated_obsolete_entities()
+                self.fixes_punning_errors()
+                self.updates_ontology_reporter()  # get finishing statistics
+                self._logically_verifies_cleaned_ontologies()
+
+        print('\n\n*** CLEANING ONTOLOGY DATA SOURCES ***')
+        self.ont_file_location = self.merged_ontology_filename
+        onts = [self.temp_dir + '/' + x for x in list(self.ontology_info.keys())
+                if x != self.merged_ontology_filename]
+        merges_ontologies(onts, self.temp_dir + '/', self.ont_file_location)
+        print('\nLoading Merged Ontology')
+        self.ont_graph = Graph().parse(self.temp_dir + '/' + self.ont_file_location)
+        self.updates_ontology_reporter()  # get starting statistics
+        self.fixes_identifier_errors()
+        self.normalizes_duplicate_classes()
+        self.normalizes_existing_classes()
+        self.fixes_punning_errors()
+        self.updates_ontology_reporter()  # get finishing statistics
+        # serializes final ontology graph and uploads graph data and ontology report to gcs
+        self.ont_graph.serialize(destination=self.temp_dir + '/' + self.ont_file_location, format='xml')
+        ontology_file_formatter(self.temp_dir, '/' + self.ont_file_location, self.owltools_location)
+        self.uploads_data_to_gcs_bucket(self.ont_file_location)
+
+        print('\nGENERATING ONTOLOGY REPORT')
+        self.generates_ontology_report()
+
+        return None
diff --git a/main.ipynb b/main.ipynb
index 43613899f..1b0443d31 100644
--- a/main.ipynb
+++ b/main.ipynb
@@ -142,7 +142,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "ont = pkt.OntData('resources/ontology_source_list.txt')\n",
+    "ont = pkt.OntData('resources/ontology_source_list.txt', 'resources/resource_info.txt')\n",
     "ont.downloads_data_from_url()\n",
     "ont.writes_source_metadata_locally()"
    ]
@@ -163,7 +163,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "edges = pkt.LinkedData('resources/edge_source_list.txt')\n",
+    "edges = pkt.LinkedData('resources/edge_source_list.txt', 'resources/resource_info.txt')\n",
     "edges.downloads_data_from_url()\n",
     "edges.writes_source_metadata_locally()"
    ]
diff --git a/pkt_kg/construction_approaches.py b/pkt_kg/construction_approaches.py
index 08a05ded3..e2a3814d6 100644
--- a/pkt_kg/construction_approaches.py
+++ b/pkt_kg/construction_approaches.py
@@ -80,11 +80,10 @@ def maps_node_to_class(self, edge_type: str, entity: str, edge: List) -> Optiona
         mapped to that non-class entity. For example:
             {'R-HSA-5601843': {'PW_0000001'}, 'R-HSA-77584': {'PW_0000001', 'GO_0008334'}}
 
-        The motivation for verifying whether a non-ontology node identifier is not
-        in the subclass_map is to try and catch errors in the edge sources used to generate the edges. For example,
-        there were genes taken from CTD that were tagged in the downloaded data as being human, that were not
-        actually human and the way that we caught that error was by checking against the identifiers included in the
-        subclass_map dict.
+        The motivation for verifying whether a non-ontology node identifier is not in the subclass_map is to try and
+        catch errors in the edge sources used to generate the edges. For example, there were genes taken from CTD
+        that were tagged in the downloaded data as being human, that were not actually human and the way that we
+        caught that error was by checking against the identifiers included in the subclass_map dict.
 
         Args:
             edge_type: A string containing the edge_type (e.g. "gene-pathway").
diff --git a/pkt_kg/utils/__init__.py b/pkt_kg/utils/__init__.py
index bf90a4d27..5102a20b4 100644
--- a/pkt_kg/utils/__init__.py
+++ b/pkt_kg/utils/__init__.py
@@ -10,5 +10,6 @@
            'gzipped_url_download', 'data_downloader', 'explodes_data', 'chunks', 'metadata_dictionary_mapper',
            'metadata_api_mapper', 'genomic_id_mapper', 'outputs_dictionary_data', 'gets_ontology_statistics',
            'gets_ontology_classes', 'gets_deprecated_ontology_classes', 'gets_object_properties',
-           'merges_ontologies', 'ontology_file_formatter', 'adds_edges_to_graph', 'remove_edges_from_graph',
+           'gets_ontology_class_dbxrefs', 'gets_ontology_class_synonyms', 'merges_ontologies',
+           'ontology_file_formatter', 'adds_edges_to_graph', 'remove_edges_from_graph',
            'finds_node_type', 'maps_node_ids_to_integers', 'converts_rdflib_to_networkx']
diff --git a/pkt_kg/utils/data_utils.py b/pkt_kg/utils/data_utils.py
index 07f774bf3..fd69bbc0b 100644
--- a/pkt_kg/utils/data_utils.py
+++ b/pkt_kg/utils/data_utils.py
@@ -271,7 +271,7 @@ def metadata_api_mapper(nodes: List[str]) -> pd.DataFrame:
 
     ids, labels, desc, synonyms = [], [], [], []
 
-    for request_ids in list(chunks(nodes, 20)):
+    for request_ids in tqdm(list(chunks(nodes, 20))):
         results = content.query_ids(ids=','.join(request_ids))
         for row in results:
             ids.append(row['stId'])
@@ -281,9 +281,8 @@ def metadata_api_mapper(nodes: List[str]) -> pd.DataFrame:
             else: synonyms.append('None')
 
     # combine into new data frame
-    node_metadata_final = pd.DataFrame(list(zip(ids, labels, desc, synonyms)),
-                                       columns=['ID', 'Label', 'Description', 'Synonym'])
-    node_metadata_final = node_metadata_final.astype(str)
+    metadata = pd.DataFrame(list(zip(ids, labels, desc, synonyms)), columns=['ID', 'Label', 'Description', 'Synonym'])
+    node_metadata_final = metadata.astype(str)
 
     return node_metadata_final
 
@@ -350,24 +349,32 @@ def genomic_id_mapper(id_dict: Dict[str, str], filename: str, genomic1: str, gen
         None.
     """
 
+    results = []
+    for key in tqdm(id_dict.keys()):
+        if genomic_type is not None:
+            ent_type = [x.split('_')[-1] for x in id_dict[key] if x.startswith(genomic_type)]
+            g_type = ent_type[0] if len(ent_type) > 0 else 'None'
+        else:
+            g_type = 'None'
+        for res in id_dict[key]:
+            if genomic1 in key and res.startswith(genomic2):
+                if prefix1 and prefix2: res1, res2 = '_'.join(key.split('_')[-2:]), '_'.join(res.split('_')[-2:])
+                elif not prefix1 and prefix2: res1, res2 = key.split('_')[-1], '_'.join(res.split('_')[-2:])
+                elif prefix1 and not prefix2: res1, res2 = '_'.join(key.split('_')[-2:]), res.split('_')[-1]
+                else: res1, res2 = key.split('_')[-1], res.split('_')[-1]
+            elif genomic2 in key and res.startswith(genomic1):
+                if prefix1 and prefix2: res1, res2 = '_'.join(res.split('_')[-2:]), '_'.join(key.split('_')[-2:])
+                elif not prefix1 and prefix2: res1, res2 = res.split('_')[-1], '_'.join(key.split('_')[-2:])
+                elif prefix1 and not prefix2: res1, res2 = '_'.join(res.split('_')[-2:]), key.split('_')[-1]
+                else: res1, res2 = res.split('_')[-1], key.split('_')[-1]
+            else:
+                continue
+            results += [[res1, res2, g_type]]
+
+    # write results locally
     with open(filename, 'w') as outfile:
-        for key in tqdm(id_dict.keys()):
-            id_data = id_dict[key]
-            g_type = [x.split('_')[-1] for x in id_data if x.startswith(genomic_type)][0] if genomic_type else 'None'
-            for res in id_data:
-                if genomic1 in key and res.startswith(genomic2):
-                    if prefix1 and prefix2: res1, res2 = '_'.join(key.split('_')[-2:]), '_'.join(res.split('_')[-2:])
-                    elif not prefix1 and prefix2: res1, res2 = key.split('_')[-1], '_'.join(res.split('_')[-2:])
-                    elif prefix1 and not prefix2: res1, res2 = '_'.join(key.split('_')[-2:]), res.split('_')[-1]
-                    else: res1, res2 = key.split('_')[-1], res.split('_')[-1]
-                elif genomic2 in key and res.startswith(genomic1):
-                    if prefix1 and prefix2: res1, res2 = '_'.join(res.split('_')[-2:]), '_'.join(key.split('_')[-2:])
-                    elif not prefix1 and prefix2: res1, res2 = res.split('_')[-1], '_'.join(key.split('_')[-2:])
-                    elif prefix1 and not prefix2: res1, res2 = '_'.join(res.split('_')[-2:]), key.split('_')[-1]
-                    else: res1, res2 = res.split('_')[-1], key.split('_')[-1]
-                else: continue
-                if 'none' not in res1.lower() and 'none' not in res2.lower():
-                    outfile.write(res1 + '\t' + res2 + '\t' + g_type + '\n')
+        for row in set(tuple(x) for x in results):
+            outfile.write(row[0] + '\t' + row[1] + '\t' + row[2] + '\n')
     outfile.close()
 
     return None
diff --git a/pkt_kg/utils/kg_utils.py b/pkt_kg/utils/kg_utils.py
index 291cf8726..80e7c3bda 100644
--- a/pkt_kg/utils/kg_utils.py
+++ b/pkt_kg/utils/kg_utils.py
@@ -8,6 +8,8 @@
 * gets_ontology_classes
 * gets_ontology_statistics
 * gets_object_properties
+* gets_ontology_class_dbxrefs
+* gets_ontology_class_synonyms
 * merges_ontologies
 * ontology_file_formatter
 
@@ -28,16 +30,19 @@
 import networkx  # type: ignore
 import os
 import os.path
-from rdflib import Graph, URIRef  # type: ignore
+from rdflib import Graph, Literal, Namespace, URIRef  # type: ignore
 from rdflib.namespace import RDF, OWL  # type: ignore
 import subprocess
 
 from tqdm import tqdm  # type: ignore
 from typing import Dict, List, Optional, Set, Tuple
 
+# set-up environment variables
+schema = Namespace('http://www.w3.org/2001/XMLSchema#')
+
 
 def gets_ontology_classes(graph: Graph) -> Set:
-    """Queries a knowledge graph and returns a list of all owl:Class objects in the graph.
+    """Queries a knowledge graph and returns a list of all owl:Class objects (excluding BNodes) in the graph.
 
     Args:
         graph: An rdflib Graph object.
@@ -49,15 +54,7 @@ def gets_ontology_classes(graph: Graph) -> Set:
         ValueError: If the query returns zero nodes with type owl:ObjectProperty.
     """
 
-    print('\nQuerying Knowledge Graph to Obtain all OWL:Class Nodes')
-
-    kg_classes = graph.query(
-        """SELECT DISTINCT ?c
-             WHERE {?c rdf:type owl:Class . }
-        """, initNs={'rdf': RDF, 'owl': OWL}
-    )
-
-    class_list = set([res[0] for res in tqdm(kg_classes) if isinstance(res[0], URIRef)])
+    class_list = {x for x in graph.subjects(RDF.type, OWL.Class) if isinstance(x, URIRef)}
     if len(class_list) > 0: return class_list
     else: raise ValueError('ERROR: No classes returned from query.')
 
@@ -70,20 +67,9 @@ def gets_deprecated_ontology_classes(graph: Graph) -> Set:
 
     Returns:
         class_list: A list of all of the deprecated OWL classes in the graph.
-
-    Raises:
-        ValueError: If the query returns zero nodes with type owl:Class.
     """
 
-    print('\nQuerying Knowledge Graph to Obtain all deprecated OWL:Class Nodes')
-
-    kg_classes = graph.query(
-        """SELECT DISTINCT ?c
-             WHERE {?c owl:deprecated true . }
-        """, initNs={'owl': OWL}
-    )
-
-    class_list = set([res[0] for res in tqdm(kg_classes) if isinstance(res[0], URIRef)])
+    class_list = {x for x in graph.subjects(OWL.deprecated, Literal('true', datatype=URIRef(schema + 'boolean')))}
 
     return class_list
 
@@ -101,19 +87,77 @@ def gets_object_properties(graph: Graph) -> Set:
         ValueError: If the query returns zero nodes with type owl:ObjectProperty.
     """
 
-    print('\nQuerying Knowledge Graph to Obtain all OWL:ObjectProperty Nodes')
-
-    kg_object_properties = graph.query(
-        """SELECT DISTINCT ?p
-             WHERE {?p rdf:type owl:ObjectProperty . }
-        """, initNs={'rdf': RDF, 'owl': OWL}
-    )
-
-    object_property_list = set([res[0] for res in tqdm(kg_object_properties) if isinstance(res[0], URIRef)])
+    object_property_list = {x for x in graph.subjects(RDF.type, OWL.ObjectProperty) if isinstance(x, URIRef)}
     if len(object_property_list) > 0: return object_property_list
     else: raise ValueError('ERROR: No object properties returned from query.')
 
 
+def gets_ontology_class_synonyms(graph: Graph) -> Tuple:
+    """Queries a knowledge graph and returns a tuple of dictionaries. The first dictionary contains all owl:Class
+    objects and their synonyms in the graph. The second dictionary contains the synonyms and their OWL synonym types.
+
+    Args:
+        graph: An rdflib Graph object.
+
+    Returns:
+        A tuple of dictionaries:
+            synonyms: A dictionary where keys are string synonyms and values are ontology URIs. An example is shown
+                below:
+                    {'modified l selenocysteine': 'http://purl.obolibrary.org/obo/SO_0001402',
+                    'modified l-selenocysteine': 'http://purl.obolibrary.org/obo/SO_0001402',
+                    'frameshift truncation': 'http://purl.obolibrary.org/obo/SO_0001910', ...}
+            synonym_type: A dictionary where keys are string synonyms and values are OWL synonym types. An example is
+                shown below:
+                    {'susceptibility to herpesvirus': 'hasExactSynonym', 'full upper lip': 'hasExactSynonym'}
+    """
+
+    class_list = [x for x in graph if 'synonym' in str(x[1]).lower() and isinstance(x[0], URIRef)]
+    synonyms = {str(x[2]).lower(): str(x[0]) for x in class_list}
+    synonym_type = {str(x[2]).lower(): str(x[1]).split('#')[-1] for x in class_list}
+
+    return synonyms, synonym_type
+
+
+def gets_ontology_class_dbxrefs(graph: Graph):
+    """Queries a knowledge graph and returns a dictionary that contains all owl:Class objects and their database
+    cross references (dbxrefs) in the graph. This function will also include concepts that have been identified as
+    exact matches. The query returns a tuple of dictionaries where the first dictionary contains the dbxrefs and
+    exact matches (URIs and labels) and the second dictionary contains the dbxref/exactmatch uris and a string
+    indicating the type (i.e. dbxref or exact match).
+
+    Assumption: That none of the hasdbxref ids overlap with any of the exactmatch ids.
+
+    Args:
+        graph: An rdflib Graph object.
+
+    Returns:
+        dbxref: A dictionary where keys are dbxref strings and values are ontology URIs. An example is shown below:
+            {'loinc:LA6690-7': 'http://purl.obolibrary.org/obo/SO_1000002',
+             'RNAMOD:055': 'http://purl.obolibrary.org/obo/SO_0001347',
+             'RNAMOD:076': 'http://purl.obolibrary.org/obo/SO_0001368',
+             'loinc:LA6700-2': 'http://purl.obolibrary.org/obo/SO_0001590', ...}
+        dbxref_type: A dictionary where keys are dbxref/exact match uris and values are string indicating if the uri
+            is for a dbxref or an exact match. An example is shown below:
+                {
+    """
+
+    # dbxrefs
+    dbxref_res = [x for x in graph if 'hasdbxref' in str(x[1]).lower() if isinstance(x[0], URIRef)]
+    dbxref_uris = {str(x[2]).lower(): str(x[0]) for x in dbxref_res}
+    dbxref_type = {str(x[2]).lower(): 'DbXref' for x in dbxref_res}
+
+    # exact match
+    exact_res = [x for x in graph if 'exactmatch' in str(x[1]).lower() if isinstance([0], URIRef)]
+    exact_uris = {str(x[2]).lower(): str(x[0]) for x in exact_res}
+    exact_type = {str(x[2]).lower(): 'ExactMatch' for x in exact_res}
+
+    # combine dictionaries
+    uris = {**dbxref_uris, **exact_uris}
+    types = {**dbxref_type, **exact_type}
+
+    return uris, types
+
+
 def gets_ontology_statistics(file_location: str, owltools_location: str = './pkt_kg/libs/owltools') -> None:
     """Uses the OWL Tools API to generate summary statistics (i.e. counts of axioms, classes, object properties, and
     individuals).
@@ -168,7 +212,7 @@ def merges_ontologies(ontology_files: List[str], write_location: str, merged_ont
         else:
             ont1, ont2 = ontology_files.pop(), ontology_files.pop()
         try:
-            print('\nMerging Ontologies: {ont1}, {ont2}'.format(ont1=ont1.split('/')[-1], ont2=ont2.split('/')[-1]))
+            print('Merging Ontologies: {ont1}, {ont2}'.format(ont1=ont1.split('/')[-1], ont2=ont2.split('/')[-1]))
             subprocess.check_call([owltools_location, str(ont1), str(ont2), '--merge-support-ontologies',
                                    '-o', write_location + merged_ont_kg])
         except subprocess.CalledProcessError as error:
@@ -196,7 +240,8 @@ def ontology_file_formatter(write_location: str, full_kg: str,
         TypeError: If the input file contains no data.
     """
 
-    print('\n*** Applying OWL API Formatting to Knowledge Graph OWL File ***')
+    print('Applying OWL API Formatting to Knowledge Graph OWL File')
+
     graph_write_location = write_location + full_kg
     if '.owl' not in graph_write_location: raise TypeError('The provided file is not type .owl')
     elif not os.path.exists(graph_write_location): raise IOError('{} does not exist!'.format(graph_write_location))
@@ -359,7 +404,7 @@ def converts_rdflib_to_networkx(write_location: str, full_kg: str, graph: Option
         IOError: If the file referenced by filename does not exist.
     """
 
-    print('\nConverting Knowledge Graph to MultiDiGraph')
+    print('Converting Knowledge Graph to MultiDiGraph')
 
     # read in knowledge graph if class graph attribute is not present
     if not isinstance(graph, Graph):
@@ -367,12 +412,14 @@ def converts_rdflib_to_networkx(write_location: str, full_kg: str, graph: Option
         file_type = 'xml' if 'OWLNETS' not in full_kg else full_kg.split('.')[-1]
         ext = '.owl' if file_type == 'xml' else '.nt'
         graph.parse(write_location + full_kg + ext, format=file_type)
+
     # convert graph to networkx object
     nx_mdg = networkx.MultiDiGraph()
     for s, p, o in tqdm(graph):
         nx_mdg.add_edge(s, o, **{'key': p})
+
     # pickle networkx graph
-    print('\nPickling MultiDiGraph. For Large Networks Process Takes Several Minutes.')
+    print('Pickling MultiDiGraph. For Large Networks Process Takes Several Minutes.')
     networkx.write_gpickle(nx_mdg, write_location + full_kg + '_NetworkxMultiDiGraph.gpickle')
     del nx_mdg   # clean up environment
 
diff --git a/resources/ontologies/ontology_source_metadata.txt b/resources/ontologies/ontology_source_metadata.txt
index b1e385705..ff670f6a7 100644
--- a/resources/ontologies/ontology_source_metadata.txt
+++ b/resources/ontologies/ontology_source_metadata.txt
@@ -1,5 +1,5 @@
 ===================================
-#Tue Dec 01 20:23:51 UTC 2020 
+#Mon Jan 04 02:49:10 UTC 2021 
 ===================================
 
 EDGE: phenotype
@@ -9,8 +9,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/hp.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 85709609
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 86014000
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/hp_with_imports.owl
 
 EDGE: go
@@ -20,8 +20,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/go.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 163745431
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 163696011
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/go_with_imports.owl
 
 EDGE: disease
@@ -31,8 +31,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/mondo.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 232288036
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 232558741
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/mondo_with_imports.owl
 
 EDGE: vaccine
@@ -42,8 +42,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/vo.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 7849707
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 7866175
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/vo_with_imports.owl
 
 EDGE: chemical
@@ -52,10 +52,10 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = ftp://ftp.ebi.ac.uk/pub/databases/chebi/ontology/chebi_lite.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 165237991
-  - DOWNLOADED_FILE_LOCATION = resources/ontologies/chebi_lite_with_imports.owl
+  - DOWNLOAD_URL = http://purl.obolibrary.org/obo/chebi.owl
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 601101705
+  - DOWNLOADED_FILE_LOCATION = resources/ontologies/chebi_with_imports.owl
 
 EDGE: anatomy
 DATA PROCESSING INFO
@@ -64,7 +64,7 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/uberon/ext.owl
-  - DOWNLOAD_DATE = 12/01/2020
+  - DOWNLOAD_DATE = 01/03/2021
   - FILE_SIZE_IN_BYTES = 77022653
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/ext_with_imports.owl
 
@@ -75,7 +75,7 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/clo.owl
-  - DOWNLOAD_DATE = 12/01/2020
+  - DOWNLOAD_DATE = 01/03/2021
   - FILE_SIZE_IN_BYTES = 119273027
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/clo_with_imports.owl
 
@@ -85,10 +85,10 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/f23zxrmorwe0x7l/human_pro_closed.owl?dl=1
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 192281150
-  - DOWNLOADED_FILE_LOCATION = resources/ontologies/human_pro_closed_with_imports.owl
+  - DOWNLOAD_URL = http://purl.obolibrary.org/obo/pr.owl
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 218003949
+  - DOWNLOADED_FILE_LOCATION = resources/ontologies/pr_with_imports.owl
 
 EDGE: genomic
 DATA PROCESSING INFO
@@ -97,8 +97,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/so.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 5115649
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 5148800
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/so_with_imports.owl
 
 EDGE: pathway
@@ -108,7 +108,7 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/pw.owl
-  - DOWNLOAD_DATE = 12/01/2020
+  - DOWNLOAD_DATE = 01/03/2021
   - FILE_SIZE_IN_BYTES = 4965358
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/pw_with_imports.owl
 
@@ -119,7 +119,7 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/ro.owl
-  - DOWNLOAD_DATE = 12/01/2020
-  - FILE_SIZE_IN_BYTES = 839260
+  - DOWNLOAD_DATE = 01/03/2021
+  - FILE_SIZE_IN_BYTES = 854334
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/ro_with_imports.owl
 
diff --git a/resources/ontology_source_list.txt b/resources/ontology_source_list.txt
index 31ce5e27f..7e8ede9bc 100644
--- a/resources/ontology_source_list.txt
+++ b/resources/ontology_source_list.txt
@@ -2,10 +2,10 @@ phenotype, http://purl.obolibrary.org/obo/hp.owl
 go, http://purl.obolibrary.org/obo/go.owl
 disease, http://purl.obolibrary.org/obo/mondo.owl
 vaccine, http://purl.obolibrary.org/obo/vo.owl
-chemical, ftp://ftp.ebi.ac.uk/pub/databases/chebi/ontology/chebi_lite.owl
+chemical, http://purl.obolibrary.org/obo/chebi.owl
 anatomy, http://purl.obolibrary.org/obo/uberon/ext.owl
 cell, http://purl.obolibrary.org/obo/clo.owl
-protein, https://www.dropbox.com/s/f23zxrmorwe0x7l/human_pro_closed.owl?dl=1
+protein, http://purl.obolibrary.org/obo/pr.owl
 genomic, http://purl.obolibrary.org/obo/so.owl
 pathway, http://purl.obolibrary.org/obo/pw.owl
 relation, http://purl.obolibrary.org/obo/ro.owl
\ No newline at end of file
diff --git a/resources/processed_data/.gitignore b/resources/processed_data/.gitignore
new file mode 100644
index 000000000..86d0cb272
--- /dev/null
+++ b/resources/processed_data/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/resources/resource_info.txt b/resources/resource_info.txt
index 569d8d785..46c726e3d 100644
--- a/resources/resource_info.txt
+++ b/resources/resource_info.txt
@@ -1,5 +1,5 @@
-chemical-disease|:;MESH_;|class-class|RO_0002606|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt;1:./resources/processed_data/DISEASE_DOID_MAP.txt|5;!=;''|None
-chemical-gene|;MESH_;|class-entity|RO_0002434|http://purl.obolibrary.org/obo/|https://www.ncbi.nlm.nih.gov/gene/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt|None|6;==;Homo sapiens::5;.startswith('gene');
+chemical-disease|:;MESH_;|class-class|RO_0002606|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt;1:./resources/processed_data/DISEASE_MONDO_MAP.txt|5;!=;''|None
+chemical-gene|;MESH_;|class-entity|RO_0002434|http://purl.obolibrary.org/obo/|http://www.ncbi.nlm.nih.gov/gene/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt|None|6;==;Homo sapiens::5;.startswith('gene');
 chemical-gobp|:;MESH_;GO_|class-class|RO_0002436|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;5|0:./resources/processed_data/MESH_CHEBI_MAP.txt|8;<;0.0001|3;==;Biological Process
 chemical-gocc|:;MESH_;GO_|class-class|RO_0002436|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;5|0:./resources/processed_data/MESH_CHEBI_MAP.txt|8;<;0.0001|3;==;Cellular Component
 chemical-gomf|:;MESH_;GO_|class-class|RO_0002436|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;5|0:./resources/processed_data/MESH_CHEBI_MAP.txt|8;<;0.0001|3;==;Molecular Function
@@ -7,13 +7,13 @@ chemical-pathway|;CHEBI_;|class-entity|RO_0000056|http://purl.obolibrary.org/obo
 chemical-phenotype|:;MESH_;|class-class|RO_0002606|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt;1:./resources/processed_data/PHENOTYPE_HPO_MAP.txt|5;!=;''|None
 chemical-protein|;MESH_;|class-class|RO_0002434|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/MESH_CHEBI_MAP.txt;1:./resources/processed_data/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt|None|6;==;Homo sapiens::5;.startswith('protein');
 chemical-rna|;MESH_;|class-entity|RO_0002434|http://purl.obolibrary.org/obo/|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|t|1;3|0:./resources/processed_data/MESH_CHEBI_MAP.txt;1:./resources/processed_data/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt|None|6;==;Homo sapiens::5;.startswith('mRNA');
-disease-phenotype|:;;HP_|class-class|RO_0002200|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/DISEASE_DOID_MAP.txt|None|None|None
-gene-disease|;;|entity-class|RO_0003302|https://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;4|1:./resources/processed_data/DISEASE_DOID_MAP.txt|10;>=;0.70|None
-gene-gene|;;|entity-entity|RO_0002435|https://www.ncbi.nlm.nih.gov/gene/|https://www.ncbi.nlm.nih.gov/gene/|t|0;1|0:./resources/processed_data/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt;1:./resources/processed_data/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt|2;>=;0.00012|None
-gene-pathway|:;;|entity-entity|RO_0000056|https://www.ncbi.nlm.nih.gov/gene/|https://reactome.org/content/detail/|t|1;3|None|None|3;.startswith('REACT:R-HSA-');
-gene-phenotype|;;|entity-class|RO_0003302|https://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;4|1:./resources/processed_data/PHENOTYPE_HPO_MAP.txt|10;>=;0.70|None
-gene-protein|;;|entity-class|RO_0002205|https://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;1|None|None|2;==;protein-coding
-gene-rna|;;|entity-entity|RO_0002511|https://www.ncbi.nlm.nih.gov/gene/|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|t|0;1|None|None|None
+disease-phenotype|:;;HP_|class-class|RO_0002200|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:./resources/processed_data/DISEASE_MONDO_MAP.txt|None|None|None
+gene-disease|;;|entity-class|RO_0003302|http://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;4|1:./resources/processed_data/DISEASE_MONDO_MAP.txt|10;>=;0.70|None
+gene-gene|;;|entity-entity|RO_0002435|http://www.ncbi.nlm.nih.gov/gene/|http://www.ncbi.nlm.nih.gov/gene/|t|0;1|0:./resources/processed_data/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt;1:./resources/processed_data/ENSEMBL_GENE_ENTREZ_GENE_MAP.txt|2;>=;0.00012|None
+gene-pathway|:;;|entity-entity|RO_0000056|http://www.ncbi.nlm.nih.gov/gene/|https://reactome.org/content/detail/|t|1;3|None|None|3;.startswith('REACT:R-HSA-');
+gene-phenotype|;;|entity-class|RO_0003302|http://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;4|1:./resources/processed_data/PHENOTYPE_HPO_MAP.txt|10;>=;0.70|None
+gene-protein|;;|entity-class|RO_0002205|http://www.ncbi.nlm.nih.gov/gene/|http://purl.obolibrary.org/obo/|t|0;1|None|None|2;==;protein-coding
+gene-rna|;;|entity-entity|RO_0002511|http://www.ncbi.nlm.nih.gov/gene/|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|t|0;1|None|None|None
 gobp-pathway|:;GO_;|class-entity|RO_0009501|http://purl.obolibrary.org/obo/|https://reactome.org/content/detail/|t|4;5|None|None|8;==;P::12;==;taxon:9606::5;.startswith('REACTOME');
 pathway-gocc|:;;GO_|entity-class|RO_0002180|https://reactome.org/content/detail/|http://purl.obolibrary.org/obo/|t|5;4|None|None|8;==;C::12;==;taxon:9606::5;.startswith('REACTOME');
 pathway-gomf|:;;GO_|entity-class|RO_0000085|https://reactome.org/content/detail/|http://purl.obolibrary.org/obo/|t|5;4|None|None|8;==;F::12;==;taxon:9606::5;.startswith('REACTOME');
@@ -29,6 +29,6 @@ protein-protein|9606.;;|class-class|RO_0002436|http://purl.obolibrary.org/obo/|h
 rna-anatomy|;;|entity-class|RO_0001025|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|http://purl.obolibrary.org/obo/|t|1;5|0:./resources/processed_data/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt;1:./resources/processed_data/HPA_GTEx_TISSUE_CELL_MAP.txt|None|3;==;Evidence at transcript level::4;==;anatomy
 rna-cell|;;|entity-class|RO_0001025|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|http://purl.obolibrary.org/obo/|t|1;5|0:./resources/processed_data/GENE_SYMBOL_ENSEMBL_TRANSCRIPT_MAP.txt;1:./resources/processed_data/HPA_GTEx_TISSUE_CELL_MAP.txt|None|3;==;Evidence at transcript level::4;==;cell line
 rna-protein|;;|entity-class|RO_0002513|https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=|http://purl.obolibrary.org/obo/|t|0;1|None|None|2;==;protein-coding
-variant-disease|:;rs;|entity-class|RO_0003302|https://www.ncbi.nlm.nih.gov/snp/|http://purl.obolibrary.org/obo/|t|9;12|1:./resources/processed_data/DISEASE_DOID_MAP.txt|24;in;["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]::7;==;1|9;!=;-1::16;==;GRCh38::8-9;dedup;desc
-variant-gene|;rs;|entity-entity|RO_0002566|https://www.ncbi.nlm.nih.gov/snp/|https://www.ncbi.nlm.nih.gov/gene/|t|9;3|None|24;in;["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]|9;!=;-1::16;==;GRCh38::8-9;dedup;desc
+variant-disease|:;rs;|entity-class|RO_0003302|https://www.ncbi.nlm.nih.gov/snp/|http://purl.obolibrary.org/obo/|t|9;12|1:./resources/processed_data/DISEASE_MONDO_MAP.txt|24;in;["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]::7;==;1|9;!=;-1::16;==;GRCh38::8-9;dedup;desc
+variant-gene|;rs;|entity-entity|RO_0002566|https://www.ncbi.nlm.nih.gov/snp/|http://www.ncbi.nlm.nih.gov/gene/|t|9;3|None|24;in;["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]|9;!=;-1::16;==;GRCh38::8-9;dedup;desc
 variant-phenotype|:;rs;|entity-class|RO_0003302|https://www.ncbi.nlm.nih.gov/snp/|http://purl.obolibrary.org/obo/|t|9;12|1:./resources/processed_data/PHENOTYPE_HPO_MAP.txt|24;in;["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]::7;==;1|9;!=;-1::16;==;GRCh38::8-9;dedup;desc
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 06a2adcd6..f97871d4a 100644
--- a/setup.py
+++ b/setup.py
@@ -73,6 +73,7 @@ def find_version(*file_paths):
                       'Cython==0.29.14',
                       'networkx==2.4',
                       'numpy==1.18.1',
+                      'openpyxl==3.0.3',
                       'pandas==1.0.5',
                       'rdflib==4.2.2',
                       'reactome2py>=0.0.8',


Test patch:
diff --git a/tests/test_kg_utils.py b/tests/test_kg_utils.py
index 0031db3de..cdb42855c 100644
--- a/tests/test_kg_utils.py
+++ b/tests/test_kg_utils.py
@@ -3,12 +3,13 @@
 import os.path
 import unittest
 
-from typing import List, Set
+from typing import Dict, List, Set
 from rdflib import Graph, URIRef, BNode
 
 from pkt_kg.utils import gets_ontology_statistics, merges_ontologies, ontology_file_formatter, \
     maps_node_ids_to_integers, adds_edges_to_graph, remove_edges_from_graph, finds_node_type, \
-    converts_rdflib_to_networkx, gets_ontology_classes, gets_deprecated_ontology_classes, gets_object_properties
+    converts_rdflib_to_networkx, gets_ontology_classes, gets_deprecated_ontology_classes, gets_object_properties,\
+    gets_ontology_class_dbxrefs, gets_ontology_class_synonyms
 
 
 class TestKGUtils(unittest.TestCase):
@@ -238,8 +239,7 @@ def test_gets_ontology_classes(self):
         """Tests the gets_ontology_classes method."""
 
         # read in ontology
-        graph = Graph()
-        graph.parse(self.good_ontology_file_location)
+        graph = Graph().parse(self.good_ontology_file_location)
 
         # retrieve classes form graph with data
         classes = gets_ontology_classes(graph)
@@ -257,8 +257,7 @@ def test_gets_deprecated_ontology_classes(self):
         """Tests the gets_deprecated_ontology_classes method."""
 
         # read in ontology
-        graph = Graph()
-        graph.parse(self.good_ontology_file_location)
+        graph = Graph().parse(self.good_ontology_file_location)
 
         # retrieve classes form graph with data
         classes = gets_deprecated_ontology_classes(graph)
@@ -272,8 +271,7 @@ def test_gets_object_properties(self):
         """Tests the gets_object_properties method."""
 
         # read in ontology
-        graph = Graph()
-        graph.parse(self.good_ontology_file_location)
+        graph = Graph().parse(self.good_ontology_file_location)
 
         # retrieve object properties form graph with data
         object_properties = gets_object_properties(graph)
@@ -286,3 +284,35 @@ def test_gets_object_properties(self):
         self.assertRaises(ValueError, gets_object_properties, no_data_graph)
 
         return None
+
+    def test_gets_ontology_class_synonyms(self):
+        """Tests the  gets_ontology_class_synonyms method."""
+
+        # read in ontology
+        graph = Graph().parse(self.good_ontology_file_location)
+
+        # retrieve object properties form graph with data
+        synonym_dict, synonym_type_dict = gets_ontology_class_synonyms(graph)
+
+        self.assertIsInstance(synonym_dict, Dict)
+        self.assertIsInstance(synonym_type_dict, Dict)
+        self.assertEqual(4056, len(synonym_dict))
+        self.assertEqual(4056, len(synonym_type_dict))
+
+        return None
+
+    def test_gets_ontology_class_dbxrefs(self):
+        """Tests the  gets_ontology_class_synonyms method."""
+
+        # read in ontology
+        graph = Graph().parse(self.good_ontology_file_location)
+
+        # retrieve object properties form graph with data
+        dbxref_dict, dbxref_type_dict = gets_ontology_class_dbxrefs(graph)
+
+        self.assertIsInstance(dbxref_dict, Dict)
+        self.assertIsInstance(dbxref_type_dict, Dict)
+        self.assertEqual(393, len(dbxref_dict))
+        self.assertEqual(393, len(dbxref_type_dict))
+
+        return None