Skip to content

Commit

Permalink
fixed rf, changed name in navbar and some more stylistic choices done
Browse files Browse the repository at this point in the history
  • Loading branch information
syedzayyan committed Jan 16, 2024
1 parent 7feac19 commit f7531f1
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 4,430 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
# QSAR IN BROWSER
# QSAR IN THE BROWSER (QITB)

### Why is QITB?
This project was borne out of a virtual screening campaign that was conducted for my MPhil thesis. I was given a biological target and told to explore AI/ML strategies to find agonists for it. I learned a ton and attended a lot of talks and conferences. There I met a man who after a very complex talk was like, do you have a UI for this where I could just press a button? And, from there this project was borne to strip all the programming and only keep the chemistry, with some options to dig into the complexities of various algorithms.
For some, it will be a quick and dirty tool to analyze various targets and drugs to work on while for others it will be their foray into cheminformatics and analysis of small molecules. The hope is to make this website a powerful and versatile tool.

### Where is QITB?
Just in your browser. All the code runs in the browser. Using WASM and JS The technologies include:

- Next.js. Don't ask me why. I think it's for SEO. SSR with normal React is horrendous. SveltKit is in its infancy compared to React and I don't know how to use Vue. I have a penchant for headaches. That too!
- (RDKit JS)[https://github.com/rdkit/rdkit/tree/master/Code/MinimalLib]. This is a custom build of the JS version with hopes for more contributions to the RDKit JS repo to import more functionalities into QITB.
- Pyodide and Scikit-Learn.
- tSNE adapted for (Karpathy's)[https://github.com/karpathy/tsnejs] JavaScript implementation.
- PCA from ml-pca

### How is QITB?
You tell? Found an issue and want to fix it? Please feel free to make a PR. I am a noob when it comes to programming so will look forward to people turning and tossing my code to make it better.
5 changes: 1 addition & 4 deletions components/DataPreProcessToolKit.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export default function DataPreProcessToolKit() {
const [fpProcessing, setFPProcessing] = useState(false);
const [fploading, setFPloading] = useState(false);

const [totalComps, setTotalComps] = useState(0);

useEffect(() => {
async function loadRDKit() {
const RDK = await initRDKit()
Expand Down Expand Up @@ -49,7 +47,6 @@ export default function DataPreProcessToolKit() {
ligand.standard_value
)));

setTotalComps(de_dup_lig.length);

if (fingerprinting) {
de_dup_lig.forEach(async (lig, i) => {
Expand Down Expand Up @@ -149,7 +146,7 @@ export default function DataPreProcessToolKit() {
}, 500);
}}>Pre-Process Data</button>
<br></br>
<p>Filtered Ligands: {totalComps}</p>
<p>Unfiltered Ligands: {ligand.length}</p>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion components/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Navbar = () => {
<nav className="navbar">
<div className="logo">
<Link href="/">
SAR IN BROWSER
QSAR IN THE BROWSER
</Link>
</div>
<ul className="navLinks">
Expand Down
209 changes: 94 additions & 115 deletions components/RandomForest.js
Original file line number Diff line number Diff line change
@@ -1,127 +1,106 @@
import { useContext, useEffect, useState } from "react";
import LigandContext from "../context/LigandContext";
import { RandomForestRegression as RFRegression } from 'ml-random-forest';
import { initRDKit } from './utils/rdkit_loader'
import * as tf from '@tensorflow/tfjs'
import * as sk from 'scikitjs'
sk.setBackend(tf)

import { initRDKit } from './utils/rdkit_loader';
import Script from "next/script";
import Loader from './Loader';
import GroupedBarChart from "./BarChart";

export default function RandomForest(){
const { ligand } = useContext(LigandContext);
const [rfRun, setRFRun] = useState(false);
const [rfMAE, setRFMAE] = useState(0);
const [rfModel, setRFModelState] = useState({})
const [testSMILES, settestSMILES] = useState('C1=NC(=C2C(=N1)N(C=N2)C3C(C(C(O3)CO)O)O)N')

const [RDKit, setRDKit] = useState(null);
const [stateOfRDKit, setStateOfRDKit] = useState(false);

const [predictedRFMAE, setPredictedRFMAE] = useState('Yet to be predicted')

useEffect(() => {
setTimeout(() => {
rfRunner();
}, 1000)

async function loadRDKit() {
const RDK = await initRDKit()
setRDKit(RDK);
setStateOfRDKit(true);
}
loadRDKit();
}, []);

const rfRunner = () => {
setRFRun(false)
var X = ligand.map((obj) => obj.fingerprint);
var y = ligand.map((obj) => obj.pKi);

const kf = new sk.KFold({ nSplits: 5 });

const mae_through_folds = []
const r2_through_folds = []
for (const { trainIndex, testIndex } of kf.split(X, y)) {
try {
const regression = new RFRegression({
seed: 3,
maxFeatures: 0.5,
replacement: false,
nEstimators: 80
});

const XTrain = Array.from(trainIndex.dataSync()).map(i => X[i]);
const yTrain= Array.from(trainIndex.dataSync()).map(index => y[index]);
regression.train(XTrain, yTrain);

const XTest = Array.from(testIndex.dataSync()).map(i => X[i]);
const yTest= Array.from(testIndex.dataSync()).map(i => y[i]);
const result = regression.predict(XTest);

let mae_test = sk.metrics.meanAbsoluteError(yTest, result);
let r2_test = sk.metrics.r2Score(yTest, result);

console.log(mae_test, r2_test)

mae_through_folds.push(mae_test)
r2_through_folds.push(r2_test)
}
finally {
trainIndex.dispose()
testIndex.dispose()
}
}
import GroupedBarChart from './BarChart';

export default function RandomForest() {
const { ligand } = useContext(LigandContext);
const [pyodide_ins, setPyodide] = useState(false)
const [pyodideState, setPyodideState] = useState(false)

const [cpuNum, setCpuNum] = useState(-1);
const [maxFeats, setMaxFeats] = useState('None');
const [criterion, setCriterion] = useState('poisson');
const [nEstimators, setNEstimators] = useState(120);

const [results, setResults] = useState([])

globalThis.fp = ligand.map((obj) => obj.fingerprint);
globalThis.pKi = ligand.map((obj) => obj.pKi);

async function pyodideLoaded() {
loadPyodide().then((pyodide) => {
pyodide.loadPackage(['scikit-learn', 'numpy']).then(() => {
setPyodide(pyodide)
setPyodideState(true)
})
})
}

function runRandForestModel() {
pyodide_ins.runPython(`
import js
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.model_selection import KFold
import numpy as np
param = {
"n_estimators": 120,
"criterion": "poisson",
"max_features" : None,
}
model = RandomForestRegressor(**param, n_jobs = -1)
const regression2 = new RFRegression({
seed: 3,
maxFeatures: 0.5,
replacement: false,
nEstimators: 80
});
X = (js.globalThis.fp).to_py()
y = (js.globalThis.pKi).to_py()
regression2.train(X, y);
setRFModelState(regression2.toJSON());
setRFMAE([mae_through_folds, r2_through_folds]);
setRFRun(true)
}
X = np.array(X)
y = np.array(y)
const predictionerRF = () => {
setPredictedRFMAE('Processing....');
const mol = RDKit.get_mol(testSMILES);
const mol_fp = mol.get_morgan_fp_as_uint8array(JSON.stringify({ radius: 2, nBits: 2048 }));
const regression = RFRegression.load(rfModel);
const result = regression.predict([mol_fp]);
setPredictedRFMAE(result);
mol?.delete()
}
kf = KFold(n_splits=10, shuffle=True)
if (!rfRun) {
return(
<div className="main-container">
<div className="container">
<Loader />
</div>
</div>
)
}
metrics = []
for train, test in kf.split(X, y):
trainX = X[train]
trainY = y[train]
testX = X[test]
testY = y[test]
params = {'n_estimators': ${nEstimators}, 'criterion': '${criterion}', 'max_features': ${maxFeats}, 'n_jobs' : ${cpuNum}}
model = RandomForestRegressor(**params)
return(
<div className="main-container">
<div className="container">
{stateOfRDKit ? (<span>RDKit is Loaded ✅</span>) : (<span>Loading RDKit</span>)}
model.fit(trainX, trainY)
pred = model.predict(testX)
metric = [mean_absolute_error(testY, pred), r2_score(testY, pred)]
<h3>Random Forest performance across 5 folds</h3>
<GroupedBarChart mae = {rfMAE[0]} r2={rfMAE[1]} />
print(metric)
metrics.append(metric)
<input className="input" type="text" placeholder = {testSMILES} onChange={(e) => settestSMILES(e.target.value)}></input>
<br></br>
<button className="button" onClick={predictionerRF}>Predict on SMILES</button>
<br></br><br></br>
<span>{predictedRFMAE}</span>
</div>
</div>
js.metrics = metrics
`
)
const results = metrics.toJs();
const results_mae = results.map((arr) => arr[0]);
const results_r2 = results.map((arr) => arr[1]);
setResults([results_mae, results_r2])
}
return (
<div className="main-container">
<Script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js" onLoad={pyodideLoaded}></Script>
{pyodideState ? (
<div className="container">
<div>
<label>N_Estimators</label>
<input className="input" value = {nEstimators} onChange={(e) => setNEstimators(e.target.value)}></input>
<br />
<label>Criterion</label>
<input className="input" value = {criterion} onChange={(e) => setCriterion(e.target.value)}></input>
<br />
<label>Max_features</label>
<input className="input" value = {maxFeats} onChange={(e) => setMaxFeats(e.target.value)}></input>
<br />
<label>CPU No:</label>
<input type = 'number' value = {cpuNum} onChange={(e) => setCpuNum(e.target.value)} className="input"></input>
</div>
<br />
<button onClick={runRandForestModel} className="button">Run Random Forest Model</button>
{results.length > 0 ? <GroupedBarChart mae = {results[0]} r2 = {results[1]} /> : <></>}
</div>
): <Loader loadingText="Loading Scikit and Numpy"/>}
</div>
)
}
4 changes: 2 additions & 2 deletions components/ThemeSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export default function ThemeSwitcher() {
/>

<div className="switch">
<img alt="theme switch to dark" className="moon" src="/sar-in-browser/moon.png"></img>
<img alt="theme switch to light" className="sun" src="/sar-in-browser/sun.png"></img>
<img alt="theme switch to dark" className="moon" src="/qsar-in-browser/moon.png"></img>
<img alt="theme switch to light" className="sun" src="/qsar-in-browser/sun.png"></img>
</div>
</label>
<Themes theme = {theme}/>
Expand Down
2 changes: 1 addition & 1 deletion components/Themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const DarkTheme = () => {
--border-color: #95a5a6; /* Light border color */
--input-back: #555; /* Darker gray for input background */
--input-color: #fbec48; /* Yellow for input text */
--input-color: #9b9e02; /* Yellow for input text */
--darker-secondary-accent-color: #004080;
}
Expand Down
29 changes: 3 additions & 26 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
const CopyPlugin = require("copy-webpack-plugin");
const { PyodidePlugin } = require("@pyodide/webpack-plugin");

const nextConfig = {
// webpack(config, { isServer }) {
// config.plugins.push(
// new CopyPlugin({
// patterns: [
// {
// from: "node_modules/@rdkit/rdkit/dist/RDKit_minimal.wasm",
// to: "static/chunks"
// }
// ]
// }),
// new PyodidePlugin(),
// );

// if (!isServer) {
// config.resolve.fallback = {
// fs: false
// };
// }

// return config;
// },

const nextConfig = {
output: "export",
basePath: "/sar-in-browser",
basePath: "/qsar-in-browser",
images: {
unoptimized: true,
},
};

module.exports = nextConfig;
module.exports = nextConfig;
Loading

0 comments on commit f7531f1

Please sign in to comment.