PubMatrixPython — full reference notebook¶

Complete walkthrough of every parameter and feature. Mirrors the PubMatrixR documentation.
Reference: Becker et al. (2003) BMC Bioinformatics 4:61. https://doi.org/10.1186/1471-2105-4-61
Overview¶
PubMatrixPython performs systematic literature searches on PubMed and PMC databases using pairwise combinations of search terms. It builds co-occurrence matrices showing the number of publications that mention both terms from two different sets, enabling researchers to explore relationships between genes, diseases, pathways, or any other biomedical concepts.
This is a Python port of the PubMatrixR R package, with added concurrency, disk caching, and configurable timeouts.
Key Features¶
- Pairwise literature search — automatically searches all combinations of terms from two lists
- Multiple database support — search PubMed or PMC via NCBI E-utilities
- Heatmap visualisation — overlap-percentage heatmaps with optional hierarchical clustering
- Export capabilities — save results as CSV (with PubMed hyperlink formulas) or ODS
- Date filtering — restrict searches to a publication date range
- Flexible input — pass term lists directly or load them from a text file
- Concurrency —
n_workersfor parallel queries, respecting NCBI rate limits - Caching —
cache_dirpersists query results to disk between runs - Progress tracking — built-in progress bar for long searches
Use Cases¶
PubMatrixPython is particularly useful for:
- Gene–disease association studies — explore literature connections between genes and diseases
- Pathway analysis — investigate co-occurrence of genes within or across biological pathways
- Drug–target research — analyse relationships between compounds and potential targets
- Systematic literature reviews — quantify research coverage across multiple topics
- Knowledge gap identification — find under-researched combinations of terms
- Bibliometric analysis — measure research activity in specific domains
Setup¶
import sys
sys.path.insert(0, '..')
import pandas as pd
import matplotlib.pyplot as plt
from pubmatrix import (
pubmatrix,
pubmatrix_from_file,
plot_pubmatrix_heatmap,
pubmatrix_heatmap,
)
NCBI API key¶
Without a key NCBI allows 3 requests/second; with a key, 10/second. Get one at https://account.ncbi.nlm.nih.gov/
API_KEY = "YOUR_KEY_HERE"
Leave as None to run without one.
API_KEY = None # replace with your key to increase rate limit
pubmatrix() — core query function¶
pubmatrix(
A, # list of str — column terms
B, # list of str — row terms
api_key = None, # NCBI API key
database = "pubmed",# "pubmed" or "pmc"
daterange = None, # [start_year, end_year]
outfile = None, # base filename for export
export_format = None,# None | "csv" | "ods"
n_tries = 2, # retries on network failure
n_workers = 1, # parallel workers for concurrent queries
timeout = 30, # HTTP request timeout in seconds
cache_dir = None, # directory to cache query results on disk
)
Returns a pandas.DataFrame — rows = B terms, columns = A terms, values = publication counts.
Basic usage¶
A = ["WNT1", "WNT2", "CTNNB1"]
B = ["obesity", "diabetes", "cancer"]
result = pubmatrix(A=A, B=B, api_key=API_KEY)
result
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.79query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:03, 1.80query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.79query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:02, 1.75query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.53query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:03<00:02, 1.41query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.51query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.44query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.51query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.55query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
Larger matrix — 7 × 7 WNT × obesity genes¶
wnt_genes = ["WNT1", "WNT2", "WNT3A", "WNT5A", "WNT7B", "CTNNB1", "DVL1"]
obesity_genes = ["LEPR", "ADIPOQ", "PPARG", "TNF", "IL6", "ADRB2", "INSR"]
result_wnt = pubmatrix(A=wnt_genes, B=obesity_genes, api_key=API_KEY)
result_wnt
Querying NCBI: 0%| | 0/49 [00:00<?, ?query/s]
Querying NCBI: 2%|▏ | 1/49 [00:00<00:26, 1.82query/s]
Querying NCBI: 4%|▍ | 2/49 [00:01<00:26, 1.79query/s]
Querying NCBI: 6%|▌ | 3/49 [00:01<00:25, 1.79query/s]
Querying NCBI: 8%|▊ | 4/49 [00:02<00:29, 1.54query/s]
Querying NCBI: 10%|█ | 5/49 [00:03<00:26, 1.63query/s]
Querying NCBI: 12%|█▏ | 6/49 [00:03<00:25, 1.68query/s]
Querying NCBI: 14%|█▍ | 7/49 [00:04<00:26, 1.57query/s]
Querying NCBI: 16%|█▋ | 8/49 [00:04<00:25, 1.62query/s]
Querying NCBI: 18%|█▊ | 9/49 [00:05<00:23, 1.67query/s]
Querying NCBI: 20%|██ | 10/49 [00:05<00:23, 1.69query/s]
Querying NCBI: 22%|██▏ | 11/49 [00:06<00:24, 1.55query/s]
Querying NCBI: 24%|██▍ | 12/49 [00:07<00:22, 1.62query/s]
Querying NCBI: 27%|██▋ | 13/49 [00:08<00:23, 1.51query/s]
Querying NCBI: 29%|██▊ | 14/49 [00:08<00:23, 1.47query/s]
Querying NCBI: 31%|███ | 15/49 [00:09<00:24, 1.39query/s]
Querying NCBI: 33%|███▎ | 16/49 [00:10<00:23, 1.39query/s]
Querying NCBI: 35%|███▍ | 17/49 [00:10<00:21, 1.48query/s]
Querying NCBI: 37%|███▋ | 18/49 [00:11<00:19, 1.56query/s]
Querying NCBI: 39%|███▉ | 19/49 [00:12<00:18, 1.63query/s]
Querying NCBI: 41%|████ | 20/49 [00:12<00:19, 1.51query/s]
Querying NCBI: 43%|████▎ | 21/49 [00:13<00:19, 1.41query/s]
Querying NCBI: 45%|████▍ | 22/49 [00:14<00:17, 1.51query/s]
Querying NCBI: 47%|████▋ | 23/49 [00:14<00:18, 1.43query/s]
Querying NCBI: 49%|████▉ | 24/49 [00:15<00:18, 1.36query/s]
Querying NCBI: 51%|█████ | 25/49 [00:16<00:16, 1.47query/s]
Querying NCBI: 53%|█████▎ | 26/49 [00:16<00:14, 1.56query/s]
Querying NCBI: 55%|█████▌ | 27/49 [00:17<00:14, 1.49query/s]
Querying NCBI: 57%|█████▋ | 28/49 [00:18<00:13, 1.58query/s]
Querying NCBI: 59%|█████▉ | 29/49 [00:18<00:13, 1.47query/s]
Querying NCBI: 61%|██████ | 30/49 [00:19<00:13, 1.39query/s]
Querying NCBI: 63%|██████▎ | 31/49 [00:20<00:12, 1.49query/s]
Querying NCBI: 65%|██████▌ | 32/49 [00:21<00:11, 1.42query/s]
Querying NCBI: 67%|██████▋ | 33/49 [00:21<00:10, 1.51query/s]
Querying NCBI: 69%|██████▉ | 34/49 [00:22<00:10, 1.45query/s]
Querying NCBI: 71%|███████▏ | 35/49 [00:22<00:09, 1.53query/s]
Querying NCBI: 73%|███████▎ | 36/49 [00:23<00:08, 1.60query/s]
Querying NCBI: 76%|███████▌ | 37/49 [00:24<00:07, 1.53query/s]
Querying NCBI: 78%|███████▊ | 38/49 [00:24<00:06, 1.62query/s]
Querying NCBI: 80%|███████▉ | 39/49 [00:25<00:06, 1.66query/s]
Querying NCBI: 82%|████████▏ | 40/49 [00:25<00:05, 1.69query/s]
Querying NCBI: 84%|████████▎ | 41/49 [00:26<00:04, 1.72query/s]
Querying NCBI: 86%|████████▌ | 42/49 [00:27<00:04, 1.58query/s]
Querying NCBI: 88%|████████▊ | 43/49 [00:28<00:04, 1.45query/s]
Querying NCBI: 90%|████████▉ | 44/49 [00:28<00:03, 1.37query/s]
Querying NCBI: 92%|█████████▏| 45/49 [00:29<00:02, 1.43query/s]
Querying NCBI: 94%|█████████▍| 46/49 [00:30<00:01, 1.51query/s]
Querying NCBI: 96%|█████████▌| 47/49 [00:30<00:01, 1.46query/s]
Querying NCBI: 98%|█████████▊| 48/49 [00:31<00:00, 1.53query/s]
Querying NCBI: 100%|██████████| 49/49 [00:31<00:00, 1.60query/s]
Querying NCBI: 100%|██████████| 49/49 [00:31<00:00, 1.53query/s]
| WNT1 | WNT2 | WNT3A | WNT5A | WNT7B | CTNNB1 | DVL1 | |
|---|---|---|---|---|---|---|---|
| LEPR | 6 | 0 | 0 | 2 | 0 | 4 | 0 |
| ADIPOQ | 2 | 0 | 0 | 6 | 0 | 10 | 0 |
| PPARG | 2 | 3 | 7 | 5 | 1 | 28 | 0 |
| TNF | 83 | 4 | 113 | 126 | 7 | 224 | 3 |
| IL6 | 75 | 7 | 88 | 146 | 10 | 163 | 3 |
| ADRB2 | 1 | 0 | 0 | 2 | 0 | 0 | 0 |
| INSR | 1 | 1 | 1 | 1 | 0 | 4 | 0 |
database parameter¶
"pubmed" (default) searches MEDLINE abstracts.
"pmc" searches full-text articles in PubMed Central — counts are typically higher.
result_pmc = pubmatrix(A=A, B=B, database="pmc", api_key=API_KEY)
result_pmc
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.82query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:03, 1.82query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.78query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:02, 1.73query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:02<00:02, 1.74query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:03<00:01, 1.57query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.62query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:04<00:00, 1.66query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.48query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.61query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 2828 | 1034 | 5024 |
| diabetes | 4476 | 1643 | 7254 |
| cancer | 12832 | 5183 | 30013 |
# Side-by-side comparison
print("PubMed:")
print(result)
print("\nPMC:")
print(result_pmc)
PubMed:
WNT1 WNT2 CTNNB1
obesity 65 6 93
diabetes 122 18 272
cancer 1286 301 8451
PMC:
WNT1 WNT2 CTNNB1
obesity 2828 1034 5024
diabetes 4476 1643 7254
cancer 12832 5183 30013
daterange parameter¶
Filter results to a publication year range. Useful for tracking how co-occurrence changes over time.
result_2000_2010 = pubmatrix(A=A, B=B, daterange=[2000, 2010], api_key=API_KEY)
result_2011_2024 = pubmatrix(A=A, B=B, daterange=[2011, 2024], api_key=API_KEY)
print("2000–2010:")
print(result_2000_2010)
print("\n2011–2024:")
print(result_2011_2024)
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.78query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:03, 1.80query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.78query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.48query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.57query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:03<00:02, 1.47query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.55query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.50query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.43query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.52query/s]
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:06, 1.23query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:05, 1.23query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:02<00:04, 1.41query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.51query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:03, 1.23query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:02, 1.23query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:05<00:01, 1.26query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:06<00:00, 1.37query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.46query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.36query/s]
2000–2010:
WNT1 WNT2 CTNNB1
obesity 1 0 5
diabetes 11 4 41
cancer 361 82 2100
2011–2024:
WNT1 WNT2 CTNNB1
obesity 60 6 79
diabetes 103 12 201
cancer 768 169 5349
Output file naming¶
When outfile and export_format are specified, the result is written to
{outfile}_result.{extension} (.csv or .ods). Each cell contains both the
publication count and a hyperlink to the corresponding PubMed search:
- CSV — Excel-compatible
HYPERLINK()formulas - ODS — embedded hyperlinks for LibreOffice/OpenOffice
Row names come from B, column names from A.
Export to CSV¶
Saves a .csv where each cell is an Excel HYPERLINK formula linking directly
to the PubMed search for that term pair.
pubmatrix(A=A, B=B, outfile="output", export_format="csv", api_key=API_KEY)
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.79query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:04, 1.46query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.58query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.62query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.66query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:03<00:01, 1.69query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.54query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.48query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.40query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.51query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
Export to ODS¶
Same as CSV but in OpenDocument Spreadsheet format, with clickable hyperlinks in LibreOffice / OpenOffice.
odfpy is an optional dependency — install with:
pip install pubmatrixpython[ods]
pubmatrix(A=A, B=B, outfile="output", export_format="ods", api_key=API_KEY)
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:06, 1.26query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:04, 1.54query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.66query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.51query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.39query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:02, 1.38query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.34query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.45query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.42query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.43query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
n_tries — retry on network failure¶
Default is 2. Increase for unstable connections.
result_retry = pubmatrix(A=A, B=B, n_tries=5, api_key=API_KEY)
result_retry
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.81query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:04, 1.48query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:02<00:04, 1.44query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.55query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.38query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:02, 1.49query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.54query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.45query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.53query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.50query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
Concurrency, timeout, and caching¶
New in v0.2.0: n_workers for parallel queries, timeout for HTTP requests,
and cache_dir to persist results on disk between runs.
n_workers — concurrent queries¶
Set n_workers > 1 to fetch multiple term pairs in parallel. NCBI rate limits
(3 req/s without an API key, 10 req/s with one) are respected automatically.
result_parallel = pubmatrix(A=A, B=B, n_workers=3, n_tries=4, api_key=API_KEY)
result_parallel
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:03<00:24, 3.03s/query]
Querying NCBI: 100%|██████████| 9/9 [00:03<00:00, 3.53query/s]
Querying NCBI: 100%|██████████| 9/9 [00:03<00:00, 2.67query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
timeout — HTTP request timeout¶
Default is 30 seconds. Lower it for fast-fail behaviour, or raise it on slow connections.
result_timeout = pubmatrix(A=A, B=B, timeout=10, api_key=API_KEY)
result_timeout
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:01<00:09, 1.20s/query]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:05, 1.21query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:02<00:04, 1.41query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.53query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.62query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:01, 1.52query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.60query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.65query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.55query/s]
Querying NCBI: 100%|██████████| 9/9 [00:06<00:00, 1.49query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
cache_dir — disk caching¶
When set, each query result is cached as JSON in cache_dir. Repeated calls
with the same terms/parameters are loaded from disk instead of re-querying NCBI.
import time
start = time.perf_counter()
result_cached = pubmatrix(A=A, B=B, cache_dir="cache", api_key=API_KEY)
first_run = time.perf_counter() - start
start = time.perf_counter()
result_cached_again = pubmatrix(A=A, B=B, cache_dir="cache", api_key=API_KEY)
second_run = time.perf_counter() - start
print(f"First run (network): {first_run:.2f}s")
print(f"Second run (cached): {second_run:.2f}s")
result_cached
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.78query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:02<00:09, 1.34s/query]
Querying NCBI: 33%|███▎ | 3/9 [00:03<00:05, 1.01query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:03<00:04, 1.20query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:04<00:02, 1.36query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:02, 1.31query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:05<00:01, 1.27query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:06<00:00, 1.26query/s]
Querying NCBI: 100%|██████████| 9/9 [00:07<00:00, 1.38query/s]
Querying NCBI: 100%|██████████| 9/9 [00:07<00:00, 1.25query/s]
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:02, 2.97query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:00<00:02, 2.95query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:02, 2.95query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:01<00:01, 2.96query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:01<00:01, 2.95query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:02<00:01, 2.95query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:02<00:00, 2.95query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:02<00:00, 2.95query/s]
Querying NCBI: 100%|██████████| 9/9 [00:03<00:00, 2.96query/s]
Querying NCBI: 100%|██████████| 9/9 [00:03<00:00, 2.95query/s]
First run (network): 7.22s Second run (cached): 3.05s
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
import shutil
shutil.rmtree("cache", ignore_errors=True)
pubmatrix_from_file() — load terms from a text file¶
File format — A terms first, # separator, then B terms:
WNT1
WNT2
CTNNB1
#
obesity
diabetes
cancer
All keyword arguments are passed through to pubmatrix().
sample_terms = "WNT1\nWNT2\nCTNNB1\n#\nobesity\ndiabetes\ncancer\n"
with open("sample_terms.txt", "w") as f:
f.write(sample_terms)
result_file = pubmatrix_from_file("sample_terms.txt", api_key=API_KEY)
result_file
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.81query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:04, 1.54query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:01<00:03, 1.64query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.49query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.58query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:03<00:01, 1.64query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.67query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.57query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.42query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.53query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 65 | 6 | 93 |
| diabetes | 122 | 18 | 272 |
| cancer | 1286 | 301 | 8451 |
# With optional arguments
result_file_dated = pubmatrix_from_file(
"sample_terms.txt",
daterange=[2015, 2024],
api_key=API_KEY,
)
result_file_dated
Querying NCBI: 0%| | 0/9 [00:00<?, ?query/s]
Querying NCBI: 11%|█ | 1/9 [00:00<00:04, 1.66query/s]
Querying NCBI: 22%|██▏ | 2/9 [00:01<00:04, 1.66query/s]
Querying NCBI: 33%|███▎ | 3/9 [00:02<00:04, 1.42query/s]
Querying NCBI: 44%|████▍ | 4/9 [00:02<00:03, 1.53query/s]
Querying NCBI: 56%|█████▌ | 5/9 [00:03<00:02, 1.45query/s]
Querying NCBI: 67%|██████▋ | 6/9 [00:04<00:02, 1.36query/s]
Querying NCBI: 78%|███████▊ | 7/9 [00:04<00:01, 1.44query/s]
Querying NCBI: 89%|████████▉ | 8/9 [00:05<00:00, 1.52query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.57query/s]
Querying NCBI: 100%|██████████| 9/9 [00:05<00:00, 1.51query/s]
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 42 | 4 | 68 |
| diabetes | 89 | 11 | 179 |
| cancer | 579 | 136 | 4404 |
import os
os.remove("sample_terms.txt")
Heatmap visualisation¶
Cell values show overlap percentage:
overlap = (intersection / union) × 100
union = row_total + col_total - intersection
This is a Jaccard-style normalisation — it accounts for terms that appear frequently on their own, so a pair like (CTNNB1, cancer) is not inflated just because both terms are common.
pubmatrix_heatmap() — quick plot with defaults¶
pubmatrix_heatmap(result)
(<Figure size 1000x800 with 2 Axes>,
<Axes: title={'center': 'PubMatrix Results'}>)
plot_pubmatrix_heatmap() — full control¶
plot_pubmatrix_heatmap(
matrix,
title = "PubMatrix Co-occurrence Heatmap",
cluster_rows = True,
cluster_cols = True,
show_numbers = True,
color_palette = None, # list of hex colours; defaults to red gradient
filename = None, # save to PNG if set
width = 10,
height = 8,
scale_font = True,
show = False, # call plt.show() after plotting
)
Returns (fig, ax) — the figure is not displayed automatically; pass show=True
or call plt.show()/fig yourself in interactive sessions.
plot_pubmatrix_heatmap(
result,
title="WNT Genes × Disease Co-occurrence",
cluster_rows=True,
cluster_cols=True,
show_numbers=True,
width=8,
height=5,
)
(<Figure size 800x500 with 2 Axes>,
<Axes: title={'center': 'WNT Genes × Disease Co-occurrence'}>)
Clustering disabled¶
plot_pubmatrix_heatmap(
result,
title="No clustering",
cluster_rows=False,
cluster_cols=False,
)
(<Figure size 1000x800 with 2 Axes>, <Axes: title={'center': 'No clustering'}>)
Numbers hidden¶
plot_pubmatrix_heatmap(
result,
title="No cell annotations",
show_numbers=False,
)
(<Figure size 1000x800 with 2 Axes>,
<Axes: title={'center': 'No cell annotations'}>)
Custom colour palette¶
Pass any list of hex colours — gradient is interpolated between them.
plot_pubmatrix_heatmap(
result,
title="Blue gradient",
color_palette=["#deebf7", "#9ecae1", "#3182bd"],
)
(<Figure size 1000x800 with 2 Axes>, <Axes: title={'center': 'Blue gradient'}>)
plot_pubmatrix_heatmap(
result,
title="Green gradient",
color_palette=["#e5f5e0", "#a1d99b", "#31a354"],
)
(<Figure size 1000x800 with 2 Axes>,
<Axes: title={'center': 'Green gradient'}>)
Save to PNG and access (fig, ax)¶
plot_pubmatrix_heatmap() returns (fig, ax) and no longer calls plt.show()
automatically. Use show=True to display interactively, or work with the
returned Figure/Axes directly.
fig, ax = plot_pubmatrix_heatmap(
result,
title="Saved heatmap",
filename="heatmap_full.png",
width=8,
height=5,
show=True,
)
print(type(fig), type(ax))
<class 'matplotlib.figure.Figure'> <class 'matplotlib.axes._axes.Axes'>
Working with the result DataFrame¶
The return value is a plain pandas.DataFrame — all standard pandas operations apply.
Summary statistics¶
print("Column sums (total co-occurrences per A term):")
print(result.sum(axis=0))
print()
print("Row sums (total co-occurrences per B term):")
print(result.sum(axis=1))
Column sums (total co-occurrences per A term): WNT1 1473 WNT2 325 CTNNB1 8816 dtype: int64 Row sums (total co-occurrences per B term): obesity 164 diabetes 412 cancer 10038 dtype: int64
Bar charts — co-occurrences per term¶
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
col_totals = result.sum(axis=0).sort_values(ascending=False)
axes[0].bar(col_totals.index, col_totals.values, color="#de2d26")
axes[0].set_title("Co-occurrences per column term (A)")
axes[0].set_ylabel("Total publication count")
axes[0].tick_params(axis="x", rotation=45)
row_totals = result.sum(axis=1).sort_values(ascending=False)
axes[1].bar(row_totals.index, row_totals.values, color="#3182bd")
axes[1].set_title("Co-occurrences per row term (B)")
axes[1].set_ylabel("Total publication count")
axes[1].tick_params(axis="x", rotation=45)
plt.tight_layout()
plt.show()
Temporal trend — comparing two date windows¶
# Reuse results computed above
diff = result_2011_2024 - result_2000_2010
print("Absolute change in co-occurrence counts (2011–2024 vs 2000–2010):")
diff
Absolute change in co-occurrence counts (2011–2024 vs 2000–2010):
| WNT1 | WNT2 | CTNNB1 | |
|---|---|---|---|
| obesity | 59 | 6 | 74 |
| diabetes | 92 | 8 | 160 |
| cancer | 407 | 87 | 3249 |
plot_pubmatrix_heatmap(
diff,
title="Change in co-occurrences: 2011–2024 vs 2000–2010",
color_palette=["#f7f7f7", "#fc8d59", "#d73027"],
cluster_rows=False,
cluster_cols=False,
width=7,
height=4,
)
(<Figure size 700x400 with 2 Axes>,
<Axes: title={'center': 'Change in co-occurrences: 2011–2024 vs 2000–2010'}>)
Save results to CSV manually¶
result.to_csv("my_results.csv")
print("Saved.")
Saved.
Performance Notes¶
- Rate limiting: NCBI allows 3 requests/second without an API key, 10/second with one.
- Search time: depends on matrix size (A × B combinations),
n_workers, and network speed. - Caching: set
cache_dirto avoid re-querying NCBI for identical term pairs across runs. - Memory usage: results are held in memory as a
pandas.DataFrame; very large matrices may require substantial RAM.
Troubleshooting¶
Empty results¶
If many searches return 0 results, try:
- Using broader search terms
- Expanding the date range
- Checking spelling of scientific terms
- Using alternative gene names or synonyms
Rate limiting errors (HTTP 429)¶
If you encounter RuntimeError from repeated 429 responses:
- Obtain and use an NCBI API key (
api_key=...) - Reduce
n_workersor the size of your search matrix - Increase
n_triesto allow more retries with backoff
Long search times¶
For large matrices:
- Consider breaking into smaller sub-searches
- Use more specific date ranges
- Use
cache_dirso repeated runs skip already-fetched pairs - Increase
n_workers(while respecting rate limits)
License & Citation¶
This project is licensed under the MIT License — see LICENSE.md for details.
If you use PubMatrixPython in your research, please cite:
Becker KG, Hosack DA, Dennis G Jr, Lempicki RA, Bright TJ, Cheadle C, Engel J. PubMatrix: a tool for multiplex literature mining. BMC Bioinformatics. 2003 Dec 10;4:61. https://doi.org/10.1186/1471-2105-4-61
Developers:
- Tyler Laird (Author, original PubMatrixR)
- Enrique Toledo (Author, maintainer)