Training My First Neural Network

Dev log #[N] — April 2026


A note on what this is. This is a walkthrough rather than a result: it explains a training run end to end, from what a neural network is up to what our numbers actually meant. The data and code are accurate as of the date above. The diagrams specified under Chart Production Notes at the end have not been drawn — they are left as a description of what would help, not as a promise that it exists.


Why I’m Doing This

The project this belongs to is an attempt to find drugs that might work for one specific person with stage IV colorectal cancer — not by reading the literature faster, but by turning a large part of it into a graph and asking a model what the graph’s shape implies. Elsewhere in these notes that argument is made properly. Here it is only the reason any of the following was worth doing.

What makes the problem tractable at all is that somebody has already shown the approach works. A 2025 paper applied it to Alzheimer’s disease and colorectal cancer, published its method, and got results specific enough to check. So the first question was not can this work but the much duller and much more answerable can we make it work here — reproduce their pipeline, run it ourselves, and see whether the numbers land close enough to theirs to trust the machinery before pointing it at a patient.

They did, mostly. This post is how, step by step, including the parts where the obvious thing turned out to be wrong.

If you had told me six months ago that I’d be running a graph neural network on a biomedical knowledge graph to generate drug repurposing candidates, I would have asked you to repeat that in English. But here we are. This post is a complete walkthrough of how we got there — including every mistake, wrong turn, and “oh, that’s what that means” moment along the way.

The scientific context: we’re trying to replicate, and then extend, a 2025 paper titled “Precision Drug Repurposing for Alzheimer’s Disease and Colorectal Cancer via Knowledge Graph Embedding” by Oğuztüzün et al. (Journal of Biomedical Informatics, doi:10.1016/j.jbi.2025.104786). The paper trains a neural network on a biomedical knowledge graph to predict which drugs might treat specific diseases — including colorectal cancer (CRC), which is relevant to us for obvious reasons.

We reproduced their training pipeline, ran it ourselves, and got results close enough to the paper to be meaningful. This post explains how, step by step.


First: What Is a Neural Network, Actually?

Before we get into the specifics, let me give a plain-English overview. I won’t assume you’ve trained one before, because I hadn’t.

The basic idea

A neural network is a mathematical function that learns patterns from data by adjusting millions of internal numbers (called weights). You show it examples, it makes predictions, you tell it how wrong it was, and it adjusts its weights to be less wrong next time. Do that enough times and it gets good at whatever pattern you’re training it on.

The name “neural network” comes from a loose analogy to neurons in a brain — each “neuron” (node) in the network receives signals from others, combines them, and passes a result forward. In practice it’s just matrix multiplication and some nonlinear functions, all the way down.

[INPUT DATA] → [LAYER 1] → [LAYER 2] → ... → [OUTPUT]
                  ↑             ↑
              (weights)     (weights)
              adjusted by backpropagation

[CHART 1: Simple neural network diagram — 3-4 layers, nodes connected by arrows, label “weights” on the connections, label “backpropagation” as a backward arrow. Keep it clean and minimal. Caption: “A neural network learns by adjusting weights based on prediction errors.”]

Training

Training has a rhythm:

  1. Forward pass — run data through the network, get a prediction
  2. Calculate loss — measure how wrong the prediction is (the “loss function”)
  3. Backward pass (backpropagation) — calculate how each weight contributed to the error
  4. Update — nudge each weight in the direction that would have made the prediction better (gradient descent)
  5. Repeat for thousands of batches

The “learning rate” controls how big each nudge is. Too large and you overshoot. Too small and you never converge.

An epoch is one complete pass through the entire training dataset. We trained for 200 epochs — meaning the model saw every training triple 200 times, updating its weights after each batch.

Embeddings

Here’s the key concept for what we’re doing: embeddings. Rather than learning to classify inputs into categories, our network learns to map every entity (drug, disease, gene…) to a point in a high-dimensional vector space. Entities that are conceptually related end up close together; unrelated ones are far apart.

Think of it like a map where similar things cluster. In our case, a drug that treats Alzheimer’s should end up near other Alzheimer’s drugs in the vector space — even if we never explicitly told the model that.

Our model uses 100-dimensional embeddings, meaning each entity is represented as a list of 100 numbers. With 61,146 entities, that’s roughly 6.1 million numbers just for the entity embeddings alone.


Knowledge Graphs: Data as a Web of Relationships

Before we could train anything, we needed data in the right shape. That shape is a knowledge graph (KG).

A knowledge graph represents facts as triples:

(subject) → [relation] → (object)

For example:

  • (Metformin) → [treats] → (type 2 diabetes)
  • (ATM gene) → [associated with] → (colorectal cancer)
  • (Cyclophosphamide) → [targets] → (DNMT1 gene)

[CHART 2: Simple knowledge graph diagram — 8-10 nodes (2-3 drugs, 2-3 genes, 2 diseases, 1 phenotype), connected by labeled arrows showing different relation types. Color-code by entity type. Caption: “A knowledge graph represents biomedical knowledge as typed nodes connected by labeled relationships.”]

The knowledge graph we used is called GP-KG (Gene-Phenotype Knowledge Graph), built by Srivastava et al. at Case Western Reserve University. It aggregates data from:

  • DrugBank — FDA-approved drug information
  • TreatKB — text-mined drug-disease pairs from clinical trials and MEDLINE
  • PhenomeBrowser — drug-phenotype associations

By the numbers:

StatValue
Total entities61,146
Total edges1,246,726
Relation types9
Drug nodes1,430
Disease nodes7,790
Gene nodes18,357

The nine relation types include treat, associate (gene-disease), target (drug-gene), and several gene-phenotype associations across human phenotype, mammalian phenotype, and GO terms.

Given a partial triple like (colorectal cancer) → [treat] → ?, the model’s job is to rank all possible drugs and put the most plausible ones at the top. That’s link prediction — predicting missing edges in the graph.

The clinical interpretation: drugs the model ranks highly for a given disease are candidates for drug repurposing — potentially treating a disease they weren’t originally approved for.


The Architecture: CompGCN + ConvE

The paper uses a two-stage neural architecture called KG-Predict, built on two components that work in sequence.

Stage 1: CompGCN (learning what each entity is)

CompGCN (Compositional Graph Convolutional Networks) is a graph neural network. Unlike standard neural networks that process independent data points, GNNs explicitly pass information across graph edges — each node “learns from its neighbors.”

Here’s how it works:

For each entity, CompGCN aggregates messages from all connected entities, weighted by the relation type. A drug connected to many cancer-related genes through target edges will absorb cancer-related signal. A gene connected to many neurodegenerative disease nodes through associate edges will look neurological.

After one round of message passing (we used gcn_layer=1), every entity has a new embedding that reflects not just itself but its entire local neighborhood in the graph.

Before CompGCN:
  Drug A: [random 100-dim vector]

After CompGCN:
  Drug A: [100-dim vector encoding its targets, associated diseases,
           phenotypes of those diseases, etc.]

[CHART 3: CompGCN message passing diagram. Show a central drug node receiving arrows from 3-4 neighbor nodes (genes, diseases). Show those neighbors’ embeddings being “aggregated” into a new drug embedding. Caption: “CompGCN propagates information from neighbors to produce context-aware entity embeddings.”]

The composition operation we used is corr (circular correlation), one of three options (along with sub for subtraction and mult for multiplication). It combines the source entity embedding with the relation embedding using a correlation operation before aggregating — making the model sensitive to which relation type the edge represents, not just that an edge exists.

Stage 2: ConvE (scoring drug-disease pairs)

Once CompGCN has produced embeddings for every entity, ConvE uses those embeddings to score candidate drug-disease pairs.

For a query (disease, TREAT_REVERSE, ?):

  1. Reshape the disease embedding and the relation embedding into 2D “images”
  2. Stack them into a single input tensor
  3. Run a 2D convolution (like image processing — the model learns local patterns in the embedding space)
  4. Flatten, apply a linear layer, and score against all 1,430 drug embeddings simultaneously
  5. Highest scores = predicted treatments
disease_embedding [200-dim]  +  relation_embedding [200-dim]
           ↓
    Reshape to [10 × 20] + [10 × 20]
           ↓
    2D Convolution (kernel size 7×7, 200 filters)
           ↓
    Flatten + Linear layer
           ↓
    Dot product with all drug embeddings
           ↓
    Score vector [1,430 drugs] → rank descending

Together, these two stages mean the model first enriches every entity’s representation with graph-neighborhood context (CompGCN), then uses those enriched representations to score drug-disease pairs (ConvE). It’s a pipeline where the graph structure informs the scoring.


Getting the Data Ready

The GP-KG data comes as a single kg.txt file — 1.2 million tab-separated triples. Before training, you need to split it into train/validation/test sets.

The split script (split_GPKG.py) does something clever: it first ensures every entity and relation type appears in at least one training triple (guaranteeing the model sees everything at least once), then randomly distributes the remaining edges into 60/20/20 train/valid/test.

We ran the split with a fixed random seed:

PYTHONHASHSEED=41504 python split_GPKG.py

Result:

SplitEdges% of total
Train770,55061.8%
Valid238,08819.1%
Test238,08819.1%

We also needed query files — one per disease we wanted to score drugs for. Each file contains one triple per candidate drug:

c0002395    TREAT_REVERSE    CID100054454
c0002395    TREAT_REVERSE    CID100002907
... (1,430 lines total, one per drug)

c0002395 is the UMLS concept ID for Alzheimer’s disease. c0009402 is colorectal cancer. The CID100XXXXXX identifiers are GP-KG’s way of encoding PubChem CIDs — more on that in the gotchas section.


Running the Training

We ran training in Google Colab on an A100 GPU. Total training time for 200 epochs: approximately 4 hours.

Our hyperparameter configuration, matching the paper exactly:

name        = 'gpkg_baseline'
opn         = 'corr'        # CompGCN composition: circular correlation
init_dim    = 100           # initial entity embedding dimension
gcn_dim     = 200           # post-CompGCN embedding dimension
gcn_layer   = 1             # one round of message passing
dropout     = 0.1
lr          = 0.001         # learning rate (Adam optimizer)
batch_size  = 128
num_epochs  = 200
seed        = 41504         # random seed (from paper)
lbl_smooth  = 0.1           # label smoothing (prevents overconfidence)
neg_num     = 1000          # negative samples per positive triple

The model’s best validation performance arrived at epoch 190, with a validation MRR of 0.4923.

[CHART 4: Training curve — two lines, one for validation loss (decreasing), one for validation MRR (increasing), x-axis is epoch (0-200), add a vertical dashed line at epoch 190 labeled “best checkpoint”. Make it look like a real training curve — loss starts high and drops fast then levels, MRR starts low and rises. You don’t have exact epoch-by-epoch numbers so make this illustrative/schematic rather than data-precise. Caption: “Training curve over 200 epochs. The model’s best validation performance arrived at epoch 190 (MRR = 0.4923).”]

The checkpoint file — essentially a frozen snapshot of every learned weight — came out at 254 MB. Inside it:

  • An entity embedding matrix: 61,146 entities × 100 dimensions
  • A relation embedding matrix: 18 relations × 100 dimensions (9 relation types × 2 directions)
  • All ConvE convolutional layer weights

After training, we ran inference immediately — scoring all 1,430 drugs for both Alzheimer’s disease and colorectal cancer. This order matters enormously, as we’ll explain.


The Gotchas

This section is for anyone trying to reproduce this themselves. Every one of these cost us at least an hour.

Gotcha 1: The 100 Million CID Mystery

Our first results were baffling. The top-ranked “drugs” had IDs like CID100002907, CID100054454. We queried PubChem for CID 100002907 and got… nothing. Then CID 100054454… nothing. These seemed to be phantom research compounds, not real drugs.

Then we looked at the paper’s own supplementary data file (case_study_predict_results.csv). Right there in the paper’s output:

CID100002907 → cyclophosphamide
CID100054454 → simvastatin

The penny dropped. GP-KG encodes PubChem CIDs by adding 100,000,000. CID 100002907 is actually PubChem CID 2907 — cyclophosphamide, a well-known chemotherapy drug. CID 100054454 is PubChem CID 54454 — simvastatin, a cholesterol drug with interesting AD evidence.

The fix: subtract 100,000,000 from every GP-KG drug ID before looking it up in PubChem. One line of code, once you know to look for it. We packaged this into a reusable lookup script.

def get_pubchem_name(cid_str: str) -> str:
    raw = int(cid_str.lower().replace("cid", ""))
    real_cid = raw - 100_000_000   # the key fix
    url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{real_cid}/property/Title/JSON"
    ...

A secondary wrinkle: not everything that looks like a drug ID is one. GP-KG includes gene names like CIDEA (a lipid droplet protein), CIDEB, and CIDEC — they start with “CID” but are not drugs. The fix there: filter on the regex ^CID\d+ (only numeric characters after “CID”).

Gotcha 2: The Silent Entity Scrambling Problem

This one took longest to diagnose, and it’s the subtlest.

When the training script builds the graph, it creates a dictionary mapping every entity name to an integer index (ent2id). Entity 0, entity 1, entity 2… up to 61,145. The neural network never sees “cyclophosphamide” — it sees index 3847 (or whatever that drug happened to get).

The problem: this mapping is not saved with the checkpoint. It’s rebuilt from scratch every time you run the training script, and because the training split is generated with Python’s random.shuffle (which is sensitive to Python’s hash randomization), the entity ordering is different every session.

So: train in Colab session A → checkpoint maps cyclophosphamide to index 3847. Load that checkpoint in Colab session B → the freshly-rebuilt ent2id maps cyclophosphamide to index 12,041. The model’s internal weights haven’t changed — but now they’re being applied to the wrong entities. Every prediction is scrambled.

We discovered this by computing a Spearman rank correlation between our local inference results and the paper’s ranked drug list:

  • Local inference (wrong ent2id): ρ = 0.067 — essentially random
  • Colab inference (correct ent2id, same session as training): ρ = 0.740 — strong correlation

The difference was entirely in whether we ran inference in the same session as training or a new one. Rule: never load a checkpoint in a new session and run inference. Train and infer in one sitting.

Gotcha 3: CUDA Checkpoint on a CPU Machine

The model was trained on a Colab A100 GPU. When we tried to load the checkpoint locally (to run quick tests), PyTorch threw:

RuntimeError: Attempting to deserialize object on a CUDA device
but torch.cuda.is_available() is False

Fix: add map_location and weights_only=False arguments to torch.load:

# Before (fails on CPU):
state = torch.load(load_path)
 
# After (works anywhere):
state = torch.load(load_path, map_location=self.device, weights_only=False)

The paper’s test.py doesn’t include these arguments. Anyone reproducing locally needs to patch this.

Gotcha 4: The Drug Query File Only Had 639 Drugs

Our initial ad_pre.txt (the file listing all drugs to score for Alzheimer’s) only had 639 entries. The fix was obvious once identified — we were only looking for entities that appeared in the TREAT relation. But many drugs have no known treatment edges at all; they’re in the graph via other relations (target, drug_hp_phenotype, etc.).

The correct approach: extract all entities matching the pattern ^CID\d+ from the entity vocabulary — regardless of which relations they appear in. That gave us the correct 1,430 drug nodes.

Gotcha 5: Windows Encoding Error

A print statement in our lookup script used a Unicode arrow character (→). Windows terminal encoding (cp1252) can’t handle it:

UnicodeEncodeError: 'charmap' codec can't encode character '\u2192' in position 12

Fix: replace → with ->. Obvious in retrospect; infuriating in the moment.

Gotcha 6: The Missing Log Directory

FileNotFoundError: [Errno 2] No such file or directory: './log/gpkg_baseline'

The test script assumes a log/ directory exists. It doesn’t create one. Fix: create log_test/ and pass -logdir ./log_test/ as an argument. Not documented anywhere.


The Results

After running inference in the same Colab session as training, here’s what the model predicted.

Alzheimer’s Disease (AD) — Our Top 10

[CHART 5: Horizontal bar chart, AD top 10. Drugs on y-axis (sorted by score, highest at top), model score on x-axis. Use a blue color scheme. Include the score value as a label on each bar.]

RankDrugModel Score
1Simvastatin0.0405
2Cyclophosphamide0.0317
3Celecoxib0.0267
4Cantide + Cisplatin0.0263
5Verapamil0.0165
6Taxol (Paclitaxel)0.0150
7Amiodarone0.0143
85-Fluorouracil0.0142
9Propranolol0.0141
10Bromocriptine0.0133

Simvastatin at #1 is interesting — there’s actually a meaningful body of literature suggesting statins may have neuroprotective effects. Celecoxib (a COX-2 inhibitor / anti-inflammatory) at #3 is plausible given the neuroinflammation hypothesis of AD. These aren’t random.

Colorectal Cancer (CRC) — Our Top 10

[CHART 6: Horizontal bar chart, CRC top 10. Same format as AD chart but use a red/orange color scheme.]

RankDrugModel Score
1Cyclophosphamide0.387
2Cantide + Cisplatin0.273
3Vincristine0.187
4Methotrexate0.116
5Mitoxantrone0.115
6Taxol (Paclitaxel)0.100
7Bleomycin sulfate0.096
8Imatinib0.083
9Sorafenib0.070
10Metformin0.069

The CRC scores are notably higher than the AD scores — cyclophosphamide scores 0.387 vs simvastatin’s 0.040 for AD. This likely reflects that GP-KG has more cancer-drug treatment edges than AD-drug treatment edges, giving the model stronger signal for CRC.

Notice what’s missing from this CRC list: Oxaliplatin, Irinotecan, and Leucovorin — the backbone of standard CRC chemotherapy. They’re not here because they’re not in GP-KG at all. The dataset was built from DrugBank’s FDA-approved drugs as of its 2022 vintage, and these specific agents weren’t represented. This is an important limitation — and one of the main reasons we’re also training on PrimeKG, which has 7,957 drug nodes (vs GP-KG’s 1,430) and does include all the missing agents.


How Close Did We Get to the Paper?

The paper’s AD top 10 looks quite different from ours:

Paper’s AD Top 10 (from case_study_predict_results.csv):

RankDrug
1Memantine
2Risperidone
3Donepezil
4Galantamine
5Rivastigmine
6Quetiapine
7Olanzapine
8Haloperidol
9Aripiprazole
10Clozapine

Those top 10 are all FDA-approved drugs currently used in AD care — acetylcholinesterase inhibitors, NMDA antagonists, and antipsychotics used for behavioral symptoms. The paper’s model nailed established treatments.

Our top 10 has simvastatin, cyclophosphamide, celecoxib. Different.

Does that mean our model is wrong? Not exactly. We measure agreement using Spearman rank correlation (ρ) — a statistic that measures how well two ranked lists agree. ρ = 1.0 means perfect agreement; ρ = 0.0 means no relationship; ρ = -1.0 means perfect inverse.

[CHART 7: Scatter plot — x-axis is “paper’s rank” (1–1430), y-axis is “our rank” (1–1430), one dot per drug (1,430 dots total). Color the top-30 in the paper in orange, our top-30 in blue, overlap in green. Draw a diagonal trend line. Include a text annotation: “ρ = 0.740”. Caption: “Rank correlation between our model’s drug rankings and the paper’s rankings for Alzheimer’s disease. ρ = 0.740 indicates strong but imperfect agreement.”]

Our result: ρ = 0.740 — strong correlation. The two models broadly agree on which drugs should rank high and which should rank low, but they disagree on the top end.

Why? The most likely explanation: our training data was generated with a different random split than the paper’s. The split_GPKG.py script shuffles edges randomly, and the paper doesn’t document their exact random seed for the split. Our model and the paper’s model saw different training/test sets — so they learned slightly different internal representations.

This is actually the well-understood problem of replication vs reproduction in machine learning:

  • Reproduction: run the exact same code on the exact same data → identical outputs
  • Replication: re-implement the method and get statistically similar results

We’re currently at replication (ρ = 0.740). Getting to reproduction requires the paper’s original train/valid/test split files — which is our next step.


What We Learned About Evaluating a Model Like This

A natural question: how do we know if ρ = 0.740 is “good”? And what metrics actually matter?

MetricWhat it measures
Spearman ρAgreement of our ranked list with the paper’s ranked list. Our reproducibility metric.
MRR (Mean Reciprocal Rank)For known treatment triples held out of training: on average, what rank does the true drug get? MRR = 1/rank. MRR = 0.5 means the true drug is rank 2 on average. Our training MRR at convergence: 0.4923.
AUROCIf you pick a random known treatment drug and a random non-treatment drug, how often does the model rank the treatment higher? 0.5 = random, 1.0 = perfect. Paper reports: 0.981.
AUPRAmong the model’s top-ranked drugs, what fraction are confirmed treatments? Stricter than AUROC when true positives are rare — which they are here. Paper reports: 0.409.

We haven’t computed AUROC and AUPR yet — those require a labeled positive set of known treatment relationships and a scoring pass over all drugs. That’s coming in the next session.


Next Steps: 100% Reproduction

Our Spearman ρ = 0.740 is encouraging but not complete. Before we do anything else — before we run hyperparameter experiments, before we train on PrimeKG, before we apply any of this to Maureen’s data — we need to achieve full reproduction.

The goal: obtain the paper’s original train/valid/test split files (or request them from the authors), retrain with those exact files, and target ρ ≥ 0.90 and AUROC within 5% of the paper’s reported 0.981.

Only once we’re that close to the paper can we be confident that deviations from the paper’s results in our future experiments are due to our intentional changes — not residual pipeline differences.

After that:

  1. Hyperparameter search — does more graph propagation (2-3 CompGCN layers) help? Does a larger embedding dimension improve AUROC? We have a parameter grid to explore.
  2. PrimeKG training — train the same architecture on PrimeKG’s 8.1 million edges and 7,957 drugs. Finally get Oxaliplatin, Irinotecan, and Leucovorin in our results.
  3. Cross-KG validation — drugs that rank highly on both GP-KG and PrimeKG are stronger candidates than those that only appear on one.
  4. Personalization — map Maureen’s specific genomic alterations (BRAF V600E, APC truncation, and others) into the graph and see how that shifts the rankings.

We’re building toward a system that doesn’t just reproduce a published result but produces a patient-specific ranked list grounded in both the graph structure and her tumor’s molecular profile. The neural network is the foundation. The work is just starting.


Technical Reference

For anyone wanting to follow the exact steps:

Data source: GP-KG from Case Western Reserve University KG-Predict project

  • Entity file: kg.txt (61,146 entities, 1,246,726 edges)
  • Split script: split_GPKG.py with PYTHONHASHSEED=41504

Model: KG-Predict (Srivastava et al., J Biomed Inform 2022)

  • Architecture: CompGCN (corr, 1 layer, 200-dim) + ConvE (200 filters, kernel 7)
  • Training: 200 epochs, lr=0.001, batch=128, seed=41504, Colab A100 GPU, ~4 hours

Checkpoint: 254 MB (epoch 190, val MRR = 0.4923)

Results:

  • AD Spearman ρ vs paper: 0.740
  • AD top drug: Simvastatin (score 0.0405)
  • CRC top drug: Cyclophosphamide (score 0.387)

Key gotcha: Run inference in the same Colab session as training. Do not reload checkpoint in a new session.

Paper: Oğuztüzün et al., “Precision Drug Repurposing for Alzheimer’s Disease and Colorectal Cancer via Knowledge Graph Embedding,” J Biomed Inform 2025, doi:10.1016/j.jbi.2025.104786


Chart Production Notes

Chart 1 — Neural network diagram Hand-drawn or use draw.io. 4 layers: input (4 nodes), hidden (6 nodes), hidden (6 nodes), output (2 nodes). Arrows between all adjacent layers. Label “weights” on a few connections. Add a curved arrow going right-to-left labeled “backpropagation.” Monochrome or minimal color.

Chart 2 — Knowledge graph Draw in draw.io or Gephi export. Nodes: 3 drugs (blue), 3 genes (green), 2 diseases (red), 1 phenotype (purple). Edges: drug→gene (target), drug→disease (treat), gene→disease (associate), gene→phenotype (gene_hp_phenotype). Use real names: Simvastatin, HMGCR, Alzheimer’s disease; Cyclophosphamide, DNMT1, Colorectal cancer.

Chart 3 — CompGCN message passing Three-step diagram: (1) Drug node with 4 neighbors connected by labeled edges; (2) Arrows from neighbors converging on drug node with “aggregate” label; (3) Drug node with new embedding labeled “context-enriched.” Keep it simple.

Chart 4 — Training curve Two y-axes (or two panels). Left: validation loss (starts ~2.5, drops to ~0.8, then levels). Right: validation MRR (starts ~0.1, rises to ~0.49, levels after epoch 150). Vertical dashed line at x=190. This is illustrative — we don’t have the epoch-by-epoch log, only the final result. Make it look plausible for this type of training run.

Chart 5 — AD top 10 bar chart (data ready)

Simvastatin:          0.0405
Cyclophosphamide:     0.0317
Celecoxib:            0.0267
Cantide + Cisplatin:  0.0263
Verapamil:            0.0165
Taxol:                0.0150
Amiodarone:           0.0143
5-Fluorouracil:       0.0142
Propranolol:          0.0141
Bromocriptine:        0.0133

Horizontal bars, longest at top, blue gradient. Label scores on bars.

Chart 6 — CRC top 10 bar chart (data ready)

Cyclophosphamide:   0.3873
Cantide + Cisplatin: 0.2728
Vincristine:         0.1866
Methotrexate:        0.1161
Mitoxantrone:        0.1150
Taxol:               0.0996
Bleomycin sulfate:   0.0961
Imatinib:            0.0834
Sorafenib:           0.0696
Metformin:           0.0692

Same format, orange/red gradient.

Chart 7 — Rank correlation scatter plot 1,430 points. You don’t have all the data points — use the overlap data you do have (the paper’s CSV has all 1,430 ranks; our predictions file has all 1,430 ranks; you can compute this in Python). The key visual: a diagonal band of points from lower-left to upper-right (showing correlation), but with scatter. Annotate ρ = 0.740. Highlight top-30 paper drugs in orange, our top-30 in blue.

To generate this chart, run:

import pandas as pd
from scipy.stats import spearmanr
import matplotlib.pyplot as plt
 
paper = pd.read_csv('docs/references/kg-predict/case_study_predict_results.csv')
paper = paper.rename(columns={'rank': 'p_rank', 'cid': 'object'})
 
ours = pd.read_csv('G:/My Drive/PDR/results/ad_predictions.txt',
                   sep='\t', names=['s','r','object','rank','score'])
 
merged = paper.merge(ours, on='object')
rho, _ = spearmanr(merged['p_rank'], merged['rank'])
 
plt.figure(figsize=(8,8))
plt.scatter(merged['p_rank'], merged['rank'], alpha=0.3, s=5, color='steelblue')
plt.xlabel("Paper's Rank")
plt.ylabel("Our Rank")
plt.title(f"AD Drug Ranking Correlation (Spearman ρ = {rho:.3f})")
plt.tight_layout()
plt.savefig('docs/rank_correlation_ad.png', dpi=150)

Next post: getting to 100% — obtaining the paper’s original split and closing the gap.