{"found":55729,"hits":[{"document":{"authors":[{"affiliation":[{"name":"DataCite"}],"contributor_roles":[],"family":"Cousijn","given":"Helena","url":"https://orcid.org/0000-0001-6660-6214"}],"blog":{"authors":[{"name":"DataCite Staff"}],"community_id":"916f4925-a9f6-4b4d-b823-c769ef054f15","created":1733529600,"current_feed_url":null,"description":"Connecting Research, Advancing Knowledge","doi":null,"favicon":"https://rogue-scholar.org/api/communities/916f4925-a9f6-4b4d-b823-c769ef054f15/logo","feed_format":"application/atom+xml","feed_url":"https://datacite.org/blog/feed/atom/","filter":null,"generator":"WordPress","home_page_url":"https://datacite.org/blog/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":null,"relative_url":null,"secure":true,"slug":"datacite","status":"active","subfield":"1710","title":"DataCite Blog - DataCite","updated":1787828838,"use_api":false},"blog_name":"DataCite Blog - DataCite","blog_slug":"datacite","content_html":"<p>As a membership organization, members are at the core of everything we do. Understanding our members and ensuring good 2-way communication is therefore extremely important. Over the last year, we have been working on clarifying and aligning DataCite's membership model. We believe this will enable us to work with all organizations in the same way and give us more insight into the organizations using our services.</p>\n<p><strong>Types of Membership </strong></p>\n<p>Previously, we distinguished between Members, Providers, Clients, and Consortia. This often led to confusion among organizations about whether or not they are a DataCite Member. Therefore, going forward, we distinguish three clear membership categories: Member-only, Direct Member, and Consortium Member. The Member-only category remains unchanged and consists of Members that support DataCite's data sharing mission, want to collaborate with DataCite and/or be part of DataCite's governance. These Members do not register DOIs.</p>\n<p>When it comes to Members that do consume DOI services, we distinguish two Member types: the Direct Member and the Consortium Member.</p>\n<p>Direct Member</p>\n<p>This type of Member is a single organization that joins DataCite as a Member in its own right and consumes DOI services. This single organization may have one or more repositories under their umbrella, but those repositories are under the same administrative structure as the organization. A Direct Member also takes on membership responsibilities such as participating in DataCite's governance.</p>\n<p><img decoding=\"async\" src=\"http://datacite.org/wp-content/uploads/2021/11/direct_member.jpg\"/></p>\n<p>Consortium Member</p>\n<p>A consortium is a group of like-minded organizations that have come together to collectively participate in DataCite's community and governance activities and to use DataCite's DOI services. A consortium is composed of two or more non-profit organizations that are under different administrative structures. Consortia are generally located in a single country or subject-based. Organizations within a consortium can work with one or more repositories that are under the same administrative structure as that organization.</p>\n<p>The entire consortium is a Member collectively, meaning that the consortium has a single representative in DataCite's governance structure, regardless of the number of organizations that make up the consortium. One organization will take on the role of Consortium Lead, with the others participating as Consortium Organizations.</p>\n<p><img decoding=\"async\" src=\"http://datacite.org/wp-content/uploads/2021/11/consortium_member.jpg\"/></p>\n<p>You can find more information about the different types of membership on our <a href=\"https://datacite.org/become.html\">membership page</a>.</p>\n<p><strong>Repositories </strong></p>\n<p>Repositories play a key role in DataCite services and are therefore a key component of the DataCite membership model. We define a repository as a service operated by research organizations, where research materials are stored, managed, and made accessible. A repository is a single unit and DataCite links the repository to information in <a href=\"https://www.re3data.org/\" rel=\"noopener noreferrer\" target=\"_blank\">re3data</a>, where additional repository metadata are available.</p>\n<p>However, we realize that not all content is hosted in repositories and therefore DataCite also includes periodicals as a designation. The term periodicals include journals, proceedings, books, blogs, and working paper series.</p>\n<p>The repository information provided will be used and displayed throughout DataCite services including search, data usage statistics, Member DOI statistics, and third-party indexing services.</p>\n<p><strong>DOI Fabrica </strong></p>\n<p>Going forward, we'll be making some changes to DOI Fabrica to align with the membership categories described above.</p>\n<p>Repositories</p>\n<p>We'll be replacing the old \"Clients\" terminology with \"Repositories\" and making a stronger connection to the repository concept by adding metadata elements from re3data to the repository settings information displayed in Fabrica. Periodicals, as their own sub-category of research material service, will be designated by a periodicals badge for easy identification.</p>\n<p>Consortia</p>\n<p>We'll implement the Consortium Member structure so that what Consortium Organizations can see and what they have permissions to administer in Fabrica aligns with their unique status according to the model. Implementing this properly will mean a brief transfer period (on the order of hours) for the DOIs belonging to these organizations. We'll be reaching out to our current Consortium Members and their constituent organizations over the next few weeks to confirm their repository details and schedule their transfer periods. For existing Members that are planning on forming Consortia, we will work with you to ensure that your current Clients are successfully migrated to Consortium Organizations with Repositories in Fabrica.</p>\n<p>Timeline</p>\n<p>We're planning on rolling the consortium and repository changes into the test system during the last week of August, and we plan to go live in production by mid-September. In the interim, there will be a brief period when the test system will reflect the new changes while the production system will not, so what you see in the two systems will not match. During this period we'd welcome your feedback in addition to our more formal testing.</p>\n<p>What will I see?</p>\n<ul>\n<li>If you're currently a Provider working with clients within your own organization, then you'll fall into the Direct Member category and will see your current Clients as Repositories.</li>\n<li>If you're currently a Provider working with clients outside of your organization, you've been contacted to discuss the possibility of forming a Consortium. For now, you'll see your Clients as Repositories.</li>\n<li>If you are a Consortium Lead, you have been contacted so your current Clients can be transferred to Consortium Organization status. These transfers will take place in September.</li>\n<li>If you're currently a Client, you will see yourself as Repository but your rights remain unchanged. If you are an independent organization please contact us to discuss a transfer to Direct Member or Consortium Organization status.</li>\n</ul>\n<p><strong>Member Information </strong></p>\n<p>With all these changes happening, it's critical for us that we have the correct contact information for each organization. This information is used on our <a href=\"https://datacite.org/\">Member page</a>, to administer Fabrica, to communicate with you, and for billing purposes. Therefore, we want to encourage all our Members, both our long-time Members and those going through changes, to <a href=\"https://doi.org/10.5438/q7r3-f935\" rel=\"noopener noreferrer\" target=\"_blank\">update your contact information</a> in your Member settings. We've recently added the ability to add multiple contacts per organization, based on relevant themes of interest. Please see DataCite's <a href=\"https://datacite.org/privacy.html\">privacy policy</a> to better understand how we use personal information. Expect reminders from us because at the end of the year we'll be replacing our current mailing lists with the information obtained through this form!</p>\n<p>We realize this is a lot of information and we want to support you throughout these changes in any way we can. We created an <a href=\"https://support.datacite.org/docs/general\">FAQ section</a> on our support site and are happy to talk to you at any time to find the best model for your organization. Please don't hesitate to contact support@datacite.org at any point during this process.</p>\n<p>The post <a href=\"https://datacite.org/blog/the-datacite-membership-model-consortia-repositories-and-more/\">The DataCite Membership model: consortia, repositories, and more</a> appeared first on <a href=\"https://datacite.org\">DataCite</a>.</p>","doi":"https://doi.org/10.5438/gk09-ba24","guid":"http://datacite.org/the-datacite-membership-model-consortia-repositories-and-more/","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1566777600,"rid":"405s4-00m63","summary":"As a membership organization, members are at the core of everything we do. Understanding our members and ensuring good 2-way communication is therefore extremely important. Over the last year, we [\u2026] The post The DataCite Membership model: consortia, repositories, and more appeared first on DataCite.","title":"The DataCite Membership model: consortia, repositories, and more","updated_at":1788766955,"url":"https://datacite.org/blog/the-datacite-membership-model-consortia-repositories-and-more/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Eden","given":"Terence","url":"https://orcid.org/0000-0002-9265-9069"}],"blog":{"authors":null,"community_id":"61ce553a-bafd-4aba-a952-d3bab5e85bcc","created":1788652800,"current_feed_url":null,"description":"Regular nonsense about tech and its effects \ud83d\ude43","doi":"https://doi.org/10.59350/shkspr","favicon":"https://rogue-scholar.org/api/communities/61ce553a-bafd-4aba-a952-d3bab5e85bcc/logo","feed_format":"application/atom+xml","feed_url":"https://shkspr.mobi/blog/feed/atom/","filter":null,"generator":"WordPress","home_page_url":"https://shkspr.mobi/blog","issn":"2753-1570","language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"shkspr","status":"active","subfield":"1712","title":"Terence Eden's Blog","updated":1788694460,"use_api":false},"blog_name":"Terence Eden's Blog","blog_slug":"shkspr","content_html":"<p>I imagine everyone here has received an unsolicited message telling them that their tax is overdue and that they urgently need to visit Genuine-Tax-Payment-Website.fart or that a parcel is delayed at customs and you can pay a small sum for its release at Almost-The-Right-Acronym.ak</p>\n<p>You know it is a scam. Most people just mark as spam and move on with their day. But a significant number of people don't. They hastily visit the site, tap in their credit card details, give it their mother's maiden name, confirm address, upload a nude selfie, and only then realise that they've been had.</p>\n<p>The Internet works at pretty close to the speed of light. You can register a .uk domain and a minute later it's accessible from the other side of the planet. Brilliant for users who want to quickly launch a website. Also brilliant for abusers who want to launch a spam campaign.</p>\n<p>By the time enough people have reported the scammers' domain as suspicious, it is too late. In the time it takes for a registrar to disable the domain, or for its name to make its way to the <a href=\"https://safebrowsing.google.com/\">Safe Browsing List</a>, a million messages have already been sent and enough people have handed over their details.</p>\n<p>We're told that \"<a href=\"https://en.wikipedia.org/wiki/The_purpose_of_a_system_is_what_it_does\">the purpose of a system is what it does</a>\". At the moment, the Domain Name System's purpose seems to be a vector for criminals to run scams on people at a terrifyingly high rate.</p>\n<h2 id=\"how-big-is-this-problem\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#how-big-is-this-problem\">How big is this problem?</a></h2>\n<p>BIG!</p>\n<p>There's a great blog post by Andrew Campling which reports on this startling claim:</p>\n<blockquote><p>The study found that at least 10% of all new gTLD domain names registered during the year had subsequently appeared on security blocklists by the time of analysis. It estimated that, taking account of subsequent blocklisting and associated domains not themselves blocklisted, the share of names registered by malicious actors <strong>may be closer to 20%</strong>.</p>\n<p><a href=\"https://labs.ripe.net/author/andrew_campling/dns-abuse-and-criminal-infrastructure-beyond-definitions-and-blocklists/\">DNS Abuse and Criminal Infrastructure: Beyond Definitions and Blocklists</a> (emphasis added)</p></blockquote>\n<p>That links to a presentation by Interisle which contains some rather shocking statistics (<a href=\"https://www.icann.org/en/blogs/details/looking-beyond-the-numbers-understanding-malicious-domain-registration-data-10-08-2026-en\">albeit with disputed methodology</a>). It looks at <em>generic</em> Top Level Domains (gTLD) - those are things like .com and .fun rather than country code TLDs (ccTLD) like .uk and .de.</p>\n<p>It says 85 million new registrations of gTLDs were made in 2025. Of those 8.5 million were added to blocklists by May 2025. It reckons that a 10% abuse rate is the likely floor for these numbers and it's probably closer to 20%. One in five newly registered domains with a gTLD are scams. That's a bloody crisis.</p>\n<p>13 TLDs had more than 50% of their registrations blocklisted.</p>\n<img alt=\"Table listing the top 13 generic Top-Level Domains (gTLDs) with the highest percentage of blocklisted, malicious new domains created in 2025. Ranked from highest to lowest blocklist percentage, top entries include .LOCKER (72.9%), .LGBT (72.2%), and .TOWN (70.2%). The table detail includes TLD operators, registration totals, and specific malicious domain metrics.\" class=\"aligncenter\" height=\"954\" src=\"https://shkspr.mobi/blog/wp-content/uploads/2026/08/gTLDs.webp\" width=\"1162\"/>\n<p>I can understand why .bid and .loan are popular with scammers. But why .mobi?! What did I ever do to you, eh?</p>\n<p>Who are the scammers registering these through?</p>\n<img alt=\"List of registrars. NameCheap, Gname, Dynadot, NameSilo, GoDaddy.\" class=\"aligncenter\" height=\"390\" src=\"https://shkspr.mobi/blog/wp-content/uploads/2026/08/registrars.webp\" width=\"910\"/>\n<p>Ah, our old friends at NameCheap. See <a href=\"https://shkspr.mobi/blog/2021/05/why-do-scammers-love-namecheap/\">Why do scammers love NameCheap?</a></p>\n<p>If those five registrars had more effective policies, it might significantly dent the scammers' ability to ply their devious wares. Or they might just move on to other registrars.</p>\n<p>As the report points out:</p>\n<blockquote><p>suspension rates for blocklisted domains were 7.4% to 16.3%.</p></blockquote>\n<p><a href=\"https://interisle.net/s/FullReport_MaliciousRegistrationsintheDomainNameMarket_2026_rev.pdf\">The full report is on the Interisle website</a>.</p>\n<h2 id=\"what-can-be-done\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#what-can-be-done\">What can be done?</a></h2>\n<p>I don't know.</p>\n<p>In the first instance, it might make sense for registrars to do strong Know Your Customer (KYC) checks on anyone buying a domain. But that stops anyone who wants to anonymously register <code>I-Hate-Nintendo.whatever</code> without risking the wrath of Intellectual Property lawyers.</p>\n<p>Also, criminals have access to stolen money and stolen cards. They can convince a hapless mule to register a domain on the criminals' behalf.</p>\n<p>Registrars could ask for an escrow payment. Pay \u20ac9 for the domain name put \u20ac900 in escrow. If your domain appears on a blocklist within the year, you forfeit the money. Criminals with stolen funds are unlikely to care but it would probably put off lots of people from getting a new domain.</p>\n<p>There are various banned words and phrases depending on the TLD. For example, <a href=\"https://shkspr.mobi/blog/2024/07/ss-tld-opening-for-direct-registrations/\">South Sudan</a> has a list of political words which they don't want associated with their .ss ccTLD.</p>\n<p>But if one gTLD bans a word, a different one might not. A scammer doesn't care if the gTLD is .arse or .elbow - they just want the start of the domain to look legitimate.</p>\n<p>Some registrars have strings that they don't allow. In fairness to NameCheap, when I tried to register <code>dwp-payments-gov-uk.pizza</code> it told me that domain was banned. It wouldn't let me get any gTLD with that name.</p>\n<p>But all it takes is one registrar to be slightly lax and the scammers get through. Increasing the complexity of the rules is also a hell of a burden on smaller registrars.</p>\n<p>Besides, it's pretty easy to get a generic enough looking domain and stick the confusing bit on a subdomain. Here are a clutch mentioned in the report:</p>\n<ul>\n<li><code>https://gov.uk-dwpaph.bond/uk/</code></li>\n<li><code>https://gov.uk-dwpcjh.bond/uk/</code></li>\n<li><code>https://gov.uk-dwpclc.bond/uk</code></li>\n<li><code>https://gov.uk-dwpclw.bond/uk</code></li>\n<li><code>https://gov.uk-dwpclj.bond/uk/</code></li>\n</ul>\n<p>Perhaps there ought to be a delay before a new domain goes live to allow people to object to it? That would give governments, banks, delivery companies, and a dozen more \"important\" organisations a right to veto any \"dodgy\" looking domain.</p>\n<p>But suppose someone wants to register <code>gov-uk-stole-my-horse.horse</code> to protest the government's cruel policy of stealing horses - is that a legitimate use of a domain? What if the Darwin Pensioner Divas - a group of elderly singers - want to take payments for their new album of goth/punk covers, can the DPD delivery company veto <code>dpd-payments.music</code>?</p>\n<p>Do we want a domain name system where powerful companies control exactly which domains we can register? If I have an idea for a domain on a Friday night do I have to wait until Monday before it can be launched? Are those companies realistically able to parse millions of domains per year and have a low false-positive rate?</p>\n<p>All of these things are possible - but all of them come with an impact on legitimate users. To be clear, I don't know what the right answer is.</p>\n<h2 id=\"what-is-icann-doing-about-it\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#what-is-icann-doing-about-it\">What is ICANN doing about it?</a></h2>\n<p>Lots! It has been a few years since I've been to an ICANN meeting, but even back then the topic of abuse was high on the agenda. They appear to be looking at ways to coordinate abuse reports between various entities, along with some other policies which should hopefully work.</p>\n<p>There are two salient points from <a href=\"https://hosted-files.sched.co/icann86/b3/TRANSC_I86SQV_Mon08June2026__GNSO-DNS%20Abuse%20Mitigation%20PDP%201%20%281%20of%204%29-en.pdf\">one of the discussions held at the recent meeting</a></p>\n<blockquote><p>If anybody thinks that in our current age of AI and as we move into different kinds of computing, DNS abuse is going to numerically stay steady and we will have a downward effect on that baseline 2027 number. I'm not sure that that's an accurate assumption. I think it's going to be the other thing, which is [\u2026] it's going to be easier to abuse the DNS.</p></blockquote>\n<p>And</p>\n<blockquote><p>Abusers are going to abuse because it's just too lucrative, because no matter what we do, they will find the way to make profit off of that, and will try to circumvent everything that we do. That is not a reason not to do it, though.</p></blockquote>\n<p>Quite!</p>\n<p>As I said, I don't know the answer to this. What I do know is, much like <a href=\"https://shkspr.mobi/blog/2025/08/is-it-possible-to-allow-sideloading-and-keep-users-safe/\">Android's app ecosystem being a haven for scammers</a>, DNS is facing a crisis. When trust in a system goes, only chaos follows.</p>\n<p>I don't want to live in a world where I have to show my passport and pay thousands of pounds to register a domain which is only available after being vetted by private interests. But I also don't want to live in a world where scammers have effectively no deterrent from abusing millions of people.</p>\n<p>The purpose of a system is what it does. I hope DNS's purpose can become less dangerous while still remaining open.</p>\n<img alt=\"\" height=\"1\" loading=\"eager\" src=\"https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=74588&amp;HTTP_REFERER=Atom\" width=\"1\"/>","doi":"https://doi.org/10.59350/395ha-fss97","guid":"https://shkspr.mobi/blog/?p=74588","image":"https://shkspr.mobi/blog/wp-content/uploads/2026/08/gTLDs.webp","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1788652800,"rid":"8hyr0-9p774","summary":"I imagine everyone here has received an unsolicited message telling them that their tax is overdue and that they urgently need to visit Genuine-Tax-Payment-Website.fart or that a parcel is delayed at customs and you can pay a small sum for its release at Almost-The-Right-Acronym.ak You know it is a scam. Most people just mark as spam and move on with their day.","tags":["/etc/","ICANN","Internet","Scam","Spam"],"title":"The purpose of DNS is to spread scams","updated_at":1788766212,"url":"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p><img alt=\"A triptych of a research chemist retraining as a data scientist, generated by Dall E 3\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/career_change/chemistry_to_data_science.png\"/></p>\n<hr/>\n<h2 id=\"seedling-time-for-a-change\">:seedling: Time for a change</h2>\n<p>In 2020 I was working as a synthetic chemist in a laboratory in Aotearoa (New Zealand). I was getting disillusioned with the work (and academia in general) and started looking for a new career direction and new challenges. After chatting with a few friends who were working as data scientists I realised that my current interests in scientific computing, statistics, and drug discovery would transfer nicely to a job as a data scientist or cheminformatician.</p>\n<p>It felt like an emerging field within biomedical research, with huge potential. Alphafold 2 had just been released and was causing a lot of excitement, but the LLM hype was still yet to fully start (ChatGPT was still a few years away). It seemed like a perfect time to make the jump to a new, exciting field that would grow in the future.</p>\n<p>When I started searching for information about making the switch I somehow felt both completely lost and unguided, and overwhelmed by the resources out there. Part of the thinking behind this post was: What do I wish I knew before setting out on this journey?</p>\n<p>I've been asked a couple of times about making the change, mostly by old colleagues looking to move from chemistry research to data science, but I hope this advice is useful whatever your background is. I'm always happy to answer questions but thought it would be useful to have something to point people to that contained my main ideas and could be used as a starting point for further questions.</p>\n<p>There's a huge amount resources out there, and no right or wrong way to make the change. This just lists my experience and what worked for me, this will be different for everyone. One of my favourite things about data science is the low bar for entry, there's so much open-source software and data, YouTube videos, and Kaggle competitions that you only really need a basic computer to get started. On top of this I realise I was also lucky to have friends and colleagues that I could ask for advice, and a supportive family who were willing to house me whilst I re-trained. Through 2020 and 2021 we were occasionally confined at home due to COVID lockdowns (though thankfully in NZ these were fairly limited compared to the rest of the world), and with not much else going on this was an idea time for some re-training.</p>\n<hr/>\n<h2 id=\"snake-learning-python\">:snake: Learning Python</h2>\n<p>As I asked around my network for advice on making this change, the first suggestion was always the same, <strong>learn to code in Python</strong>. So much data science work is done using Python libraries (pandas, numpy, scikit-learn, pytorch, and rdkit for chemistry). I now spend a significant part of each day writing and reading Python code and documentation.</p>\n<p>I did what I often do (probably a bit too much) and scrolled reddit. I kept seeing two online Python courses recommended. Automate the Boring Stuff with Python Programming by Al Sweigart, and 100 Days of Code: The Complete Python Pro Bootcamp by Dr. Angela Yu. Both were available on <a href=\"https://www.udemy.com\">Udemy</a>, which seems to regularly have special offers and I completed both courses for around $10 (US) each. There are definitely similar courses available for free so it's a personal preference what to go for. I chose the paid courses for two reasons:</p>\n<ol>\n<li>I generally found them to be more organised with a clear progression, including problem sets, projects, and with good coverage of all the important topics.</li>\n<li>With a free course it's easy to stop, especially if something comes up that stops you doing it for a few days and you get out of the habit (I can be pretty lazy). By paying for the course (even a fairly low cost of $10) was enough to manipulate myself into finishing (I'm getting my money worth).</li>\n</ol>\n<h3 id=\"automate-the-boring-stuff\">Automate the boring stuff</h3>\n<p>The first course I completed was <a href=\"https://www.udemy.com/course/automate/\">Automate the Boring Stuff with Python Programming by Al Sweigart</a>. It's a fairly short course but very well taught, designed for anyone who works regularly with a computer and wants to automate repetitive tasks, so they never have to do them again. It doesn't go too deep into theory or style and gets stuck in with practical applications quickly. Within a couple of days, I was building small programs and could start to see how I could use it for to automate parts of my job. Before starting I had no idea if I would even enjoy programming in Python, and this was an ideal introduction (turns out I did enjoy it) and gave me enough understanding to look at other courses that might be useful. It seemed perfect to make my current job less boring, but not enough for the new job I wanted, so I started looking for something more in-depth.</p>\n<h3 id=\"100-days-of-code\">100 days of code</h3>\n<p>The next course I completed was <a href=\"https://www.udemy.com/course/100-days-of-code/\">100 Days of Code: The Complete Python Pro Bootcamp by Dr. Angela Yu</a>. This is a much more in-depth course that covers a huge range of applications for Python. Every day has a couple of hours of lectures that cover theory or usage, and a few problem sets to apply what you've learnt. Every 10 days there's a small project that uses everything you've learnt, and at the end there's about 20 days of projects to construct a personal Python portfolio. I found some sections a little confusing (the first time OOP is introduced), and there's a large section borrowed from a course on web development that I felt dragged on a bit (it's useful to understand HTML, CSS, and JS for things like web-scraping, but I didn't need quite that much detail on building websites). There's a significant section towards the end on data science that was particularly useful. Overall, this course probably made the biggest difference in my re-training.</p>\n<h3 id=\"other-resources\">Other resources</h3>\n<ul>\n<li><a href=\"https://cs50.harvard.edu/python/2022/\">CS50 Python from Harvard University</a>: I haven't completed this but if the quality is comparable to CS50 and CS50 SQL (discussed below) it'll be a very good course for learning Python. David Malan is a fantastic lecturer and each week there's problem sets. It's free to complete online, but you could pay for a certificate if you wanted to.</li>\n<li><a href=\"https://www.codewars.com/\">Codewars</a>: Lots of coding problems with a range of difficulties. I used these at the start to practice my Python programming, and still occasionally use the SQL problems to brush up as I don't use SQL regularly at my job.</li>\n<li><a href=\"https://adventofcode.com\">Advent of Code</a>: A daily problem during advent that gets progressively more difficult. Can be completed in any programming language.</li>\n<li><a href=\"https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/\">Fluent Python</a>: For a more advanced understanding of Python</li>\n</ul>\n<hr/>\n<h2 id=\"bar_chart-data-science-experience\">:bar_chart: Data science experience</h2>\n<p><img alt=\"I can't get a job because I don't have experience because I can't get a job\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/career_change/cant_get_job.png\"/></p>\n<p>At this stage I started thinking about jobs, but everyone I spoke to gave the same response: \"What you're doing is the right idea, but we're just looking for someone with more experience.\" This seems to be a classic problem when re-training, how do you get your first experience when everyone wants someone with experience? There are a few ways to go about this, but the main two seem to be either an internship or a bootcamp. Both have pros and cons, and I decided to go down the bootcamp route. To me the main advantages were the ease of organisation (I was relocating to the UK at the time and having something waiting for me was nice), and the shorter duration (most internships seemed to be multiple months, whereas the bootcamp I did was 5 weeks).</p>\n<h3 id=\"s2ds\">S2DS</h3>\n<p>At the recommendation of a friend, I applied to <a href=\"https://www.s2ds.org\">Science to Data Science (S2DS)</a>, a 5-week, intensive, project-based bootcamp for research scientists with a PhD or MSc looking to move into data science. The application involved a short technical project to test your exploratory data analysis skills and a behavioural interview (that seemed mostly to filter out psychopaths).</p>\n<p>For the 5 weeks you're part of a small group that gets paired with a company, working on a data science project. There's a couple of talks in the first week about best practices, but you're working on your project straight away. My team was paired with Deutsche Welle (DW), a German broadcaster. They were interested in the gender breakdown of people mentioned and quoted in their articles, so the project involved Natural Language Processing (NLP) and Named Entity Recognition (NER), as well as using techniques like web-scraping for data gathering and machine learning classifiers for predicting gender from the name.</p>\n<p>Working in a team made the experience a lot of fun. We all came from a range of backgrounds and had different skillsets, but all started the bootcamp from similar positions so were learning together. It also gave a first experience using git and github collaboratively. In addition to the team, we had a mentor from S2DS who helped with problems, and contacts with DW who helped with project direction.</p>\n<p>When I applied S2DS was \u00a3800 for the entire course, not cheap, but much cheaper than many bootcamps I saw advertised online. I made the calculation that getting a job and starting to get paid more quickly would pay off in the long run. I've heard bad things about some bootcamps found online, so always do your research before signing up. If you've got the connections or are willing to put the work in organising it yourself, an internship is another great way to gain experience without the up-front cost.</p>\n<p>My experience with S2DS was positive, and that seems to invariably be the feeling among other alumni. Having this experience on my CV and a project to talk about in interviews was invaluable during my job hunt. If you do decide to sign up, feel free to put me as your reference, I think I get some Amazon vouchers or similar. One unique advantage from completing S2DS is the ongoing career advice and the community of alumni, which will be useful going forwards in my career.</p>\n<h3 id=\"cs50x-and-cs50-sql\">CS50x and CS50 SQL</h3>\n<p>Two other online courses I completed were <a href=\"https://cs50.harvard.edu/x/\">CS50x</a> and <a href=\"https://cs50.harvard.edu/sql/\">CS50 SQL</a> from Harvard.</p>\n<p>CS50x is Harvard's introductory computer science course. It's an 11-week course with lectures, short videos on specific subjects, and problem sets each week. Whilst not essential information for a data scientist, I feel having a much better understanding of how a computer works has made me a better data scientist. The first few weeks working through problems using C made me appreciate how easy Python is!</p>\n<p>CS50 SQL is a 7-week SQL course with the same structure that teaches you all the basics you'll need for a job in data science. Most of the 7 weeks are done using SQLite, but it later moves onto ProgreSQL and MySQL.</p>\n<h3 id=\"other-resources-and-ideas\">Other resources and ideas</h3>\n<ul>\n<li><a href=\"https://www.oreilly.com/library/view/python-for-data/9781449323592/\">Python for Data Analysis</a>: Everything you need to know about the Pandas library.</li>\n<li><a href=\"https://www.oreilly.com/library/view/practical-statistics-for/9781491952955/\">Practical Statistics for Data Scientists</a>: Since many data scientists moved into the field from other areas of research, they lack the statistical understanding required (me included), brushing on the statistical rigour required to properly understand results is a good idea.</li>\n<li><a href=\"https://www.oreilly.com/library/view/data-science-from/9781492041122/\">Data Science from Scratch</a>: A good introduction to Data Science using Python.</li>\n<li>Personal projects: A portfolio on you Github is good for showing off your ability. After watching a few lectures from the <a href=\"https://www.fast.ai\">FastAI course</a> by Jeremy Howard I decided to create a computer vision classifier for chemical compounds.</li>\n<li><a href=\"https://www.kaggle.com\">Kaggle competitions</a>: I've never actually competed in any, but they seem very popular.</li>\n</ul>\n<hr/>\n<h2 id=\"pill-cheminformatics\">:pill: Cheminformatics</h2>\n<p>With a background in chemistry research, cheminformatics seemed like a natural field for me. Giving me the opportunity to combine by experience in chemistry and drug discovery with my new skills in Python programming and data science. I looked for short online courses and textbooks to get experience with cheminformatics problems and gain an understanding of the basics.</p>\n<p>I started with the <a href=\"https://chem.libretexts.org/Courses/Intercollegiate_Courses/Cheminformatics\">Cheminformatics OLCC</a>. This is an introductory course with 8 sections that cover the basics of cheminformatics. It starts with help setting up a Python environment, and covers topics such as representing molecules, chemical databases, QSAR modelling, and simple machine learning for chemistry.</p>\n<p>I then worked through <a href=\"https://www.oreilly.com/library/view/deep-learning-for/9781492039822/\">Deep Learning for the Life Sciences</a>. As datasets in the life sciences get larger, deep learning becomes more powerful. This covers how deep learning is used on molecules, proteins, and nucleic acids, including code so you can follow along.</p>\n<p>I also attended the <a href=\"https://www.ai4science.network\">AI4SD</a> Machine Learning Summer School at the University of Southampton. This was a week of lectures on topics such as machine learning, github, and LaTeX, with a focus on applications in chemistry. The week ended with a hackathon where we worked as a team to solve a chemical property prediction problem.</p>\n<p><a href=\"https://volkamerlab.org/projects/teachopencadd/\">TeachOpenCADD</a> looks like another great resource. I've not yet had the chance to go through it in detail, but it has a wide range of Jupyter Notebooks on all sorts of cheminformatics topics.</p>\n<p>I found attending meetings and conferences on AI and cheminformatics was a great way to learn about current areas of research and meet interesting people doing similar research. This list is mostly limited to my local area (Cambridgeshire, UK).</p>\n<ul>\n<li><a href=\"http://c-inf.net/\">Cambridge Cheminformatics Network Meetings</a> run every quarter with three speakers presenting their work. Free to attend in person or virtually via zoom. There's a \"networking\" opportunity (a pub trip) afterwards.</li>\n<li><a href=\"https://ukqsar.org/index.php/category/meetings/\">UK QSAR meetings</a> are twice a year and free to attend with high quality speakers.</li>\n<li><a href=\"https://www.rscbmcs.org/events/aichem7/\">The RSC AI in Chemistry Conference</a> is a multi-day annual meeting with speakers from all over the world presenting the cutting edge of research.</li>\n<li><a href=\"https://www.milner.cam.ac.uk/ai-club/\">Cambridge AI Club for Biomedicine</a> - I've not been to this one yet, but the topics discussed at previous meetings look interesting.</li>\n<li><a href=\"https://psolsson.github.io/AI4ScienceSeminar\">Chalmers AI4Science Seminars</a> are monthly virtual meetings where early-career researchers present their work using machine learning for scientific research.</li>\n</ul>\n<p>There's a load of useful blogs and newsletters for keeping up to date with cheminformatics:</p>\n<ul>\n<li><a href=\"http://www.drugdiscovery.net/\">DrugDiscovery.NET - AI in Drug Discovery</a> - A newsletter from Andreas Bender. It contains interesting links, details of events, and job listings in cheminformatics.</li>\n<li><a href=\"https://practicalcheminformatics.blogspot.com/\">Practical Cheminformatics</a> by Pat Walters. Some useful posts for common cheminformatics projects and issues.</li>\n<li><a href=\"https://greglandrum.github.io/rdkit-blog/index.html\">The RDKit Blog</a> Greg Landrum. RDKit is probably the most useful Python library for a cheminformatician, this blog has some great tips.</li>\n<li><a href=\"https://www.cheminformania.com/\">Cheminfomania</a></li>\n<li><a href=\"https://www.blopig.com/blog/\">Oxford Protein Informatics Group</a></li>\n<li><a href=\"https://iwatobipen.wordpress.com\">Is Life Worth Living?</a></li>\n</ul>\n<p>And a few useful journals to add to your RSS feed:</p>\n<ul>\n<li><a href=\"https://jcheminf.biomedcentral.com\">Journal of Cheminformatics</a></li>\n<li><a href=\"https://onlinelibrary.wiley.com/journal/18681751\">Molecular Informatics</a></li>\n<li><a href=\"https://www.sciencedirect.com/journal/artificial-intelligence-in-the-life-sciences\">Artificial Intelligence in the Life Sciences</a></li>\n<li><a href=\"https://pubs.acs.org/journal/jcisd8\">Journal of Chemical Information and Modeling</a></li>\n<li><a href=\"https://link.springer.com/journal/10822\">Journal of Computer-Aided Molecular Design</a></li>\n</ul>\n<hr/>\n<h2 id=\"memo-summary\">:memo: Summary</h2>\n<p>When I started writing this post, I didn't expect it to be quite so long! Looking back at the process of re-training I realise how much work it took, and how lucky I was to have family to help support me through it all. I'm also incredibly glad I did it! My current job suits me so much more than the lab work I was doing before.</p>\n<p>I think the general process I went through worked well, but there are still a few changes I would make if I could do it all again. I've heard that getting your first data science job has three equally important parts:</p>\n<ol>\n<li>Your skills (e.g. Python, SQL, etc.)</li>\n<li>Your portfolio and experience (e.g. Boot-camps, personal projects, internships)</li>\n<li>Your network (Friends, colleagues, recruiters)</li>\n</ol>\n<p>In the search for my first job, I over-prioritised improving my skills, spending a lot of time doing courses. Whilst this is important for succeeding at a job, getting the job requires a wider focus. If I were to do it again, I would spend more time on personal projects to put on my github, and networking with other data scientists, asking about what problems their companies have and how I might be able to help fix them. When I finally did get my first data science job, it's no surprise it was on the recommendation of a previous colleague.</p>","doi":"https://doi.org/10.59350/qfdrh-57d64","guid":"https://jonswain.github.io/I-want-to-become-a-data-scientist-but-I-have-no-idea-where-to-start","image":"https://jonswain.github.io/images/career_change/chemistry_to_data_science.png","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1714867200,"rid":"s8tvz-zy751","summary":":seedling: Time for a change In 2020 I was working as a synthetic chemist in a laboratory in Aotearoa (New Zealand). I was getting disillusioned with the work (and academia in general) and started looking for a new career direction and new challenges.","tags":["Data-science","Cheminformatics","Career-development"],"title":"I Want to Become a Data Scientist, but I Have No Idea Where to Start\u2026","updated_at":1788763540,"url":"https://jonswain.github.io/become-a-data-scientist-starting-guide/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>This is part 1 of a planned three post series on working with large chemical libraries.\nThe notebook used to create this post and all the files can be found in <a href=\"https://github.com/jonswain/active-learning\">this github repo</a>.</p>\n<hr/>\n<h2 id=\"chemical-space\">Chemical space</h2>\n<p>Estimates of the size of <a href=\"https://en.wikipedia.org/wiki/Chemical_space\">chemical space</a> (the number of different molecules that could exist) range from 10<sup>50</sup> - 10<sup>80</sup> possible compounds, with the upper limits approaching estimates of the <a href=\"https://en.wikipedia.org/wiki/Observable_universe\">total number of atoms in the universe</a> (around 10<sup>80</sup> atoms). It's never going to be possible to make and test every possible chemical compound, even if we limit it to only <a href=\"https://en.wikipedia.org/wiki/Druglikeness\">\"drug-like\" compounds</a>. Even with fast computational methods it's not always possible to exhaustively screen virtual libraries. We need methods to find the best scoring compounds from within virtual libraries without scoring every single compound.</p>\n<p>Small companies may have hundreds of thousands to millions of physical compounds in their virtual library, and larger companies will have libraries of a few million. Virtual make-on-demand libraries, especially combinatorial libraries, are expanding into the billions of compounds (often called ultra-large libraries). <a href=\"https://enamine.net/compound-collections/real-compounds/real-space-navigator\">Enamine REAL</a> has 48 billion possible compounds. If your scoring function takes one second per compound, you could maybe screen one million compounds (but it will take 11.6 days), but a billion seconds is nearly 32 years. As the size of virtual libraries expands, it becomes impossible to screen them exhaustively.</p>\n<p>There are a range of methods for filtering the library for screening, such as similarity searching or Thompson sampling. In this post I'll discuss active learning.</p>\n<h2 id=\"active-learning\">Active learning</h2>\n<p>Active learning is a machine learning method for searching large libraries when the scoring function that is too computationally expensive to label the full library of compounds. A selection of data is labelled with the expensive scoring function, and a machine learning model is trained on these labels and used to score all compounds from within the library. The compounds with the best scores from the machine learning model are labelled using the more expensive function, and the data from this pooled and used to train a new machine learning model. This cycle is repeated until a finish criterion is met.</p>\n<p>The initial steps are:</p>\n<ol>\n<li>A random compound is selected as a reference compound.</li>\n<li>A random sample of the unlabelled data is selected and labelled using the expensive scoring function.</li>\n<li>These labelled datapoints are used to train a simple machine learning model.</li>\n</ol>\n<p>Followed then by a repeating cycle:</p>\n<ol>\n<li>The machine learning model is used to score the entire library.</li>\n<li>The compounds with the highest scores from the machine learning model are labelled using the expensive scoring function.</li>\n<li>The labels from the expensive scoring function are pooled and the machine learning model is re-trained.</li>\n</ol>\n<hr/>\n<h2 id=\"imports\">Imports</h2>\n<p>First we need to import the libraries we will be using.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">gzip</span>\n<span class=\"kn\">import</span> <span class=\"nn\">math</span>\n<span class=\"kn\">import</span> <span class=\"nn\">re</span>\n<span class=\"kn\">import</span> <span class=\"nn\">shutil</span>\n<span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">functools</span> <span class=\"kn\">import</span> <span class=\"n\">partial</span>\n<span class=\"kn\">from</span> <span class=\"nn\">itertools</span> <span class=\"kn\">import</span> <span class=\"n\">product</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">matplotlib.pyplot</span> <span class=\"k\">as</span> <span class=\"n\">plt</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">import</span> <span class=\"nn\">seaborn</span> <span class=\"k\">as</span> <span class=\"n\">sns</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">AllChem</span><span class=\"p\">,</span> <span class=\"n\">DataStructs</span><span class=\"p\">,</span> <span class=\"n\">Descriptors</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.ensemble</span> <span class=\"kn\">import</span> <span class=\"n\">RandomForestRegressor</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n</code></pre></div></div>\n<h2 id=\"expensive-scoring-function\">Expensive scoring function</h2>\n<p>The first function we will define is the expensive scoring function, this will take a list of SMILES and return a list of scores. For this example, I'm going to try find the compound from within the library with the lowest calculated Log P. This is actually a very fast calculation and can be done exhaustively, which means we can confirm if active learning is finding the lowest value.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculates the logP value for a list of compounds.\n\n    Args:\n        smiles (pd.Series): The input molecules.\n\n    Returns:\n        np.arary: The scores of the molecules.\n    \"\"\"</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">))</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">smiles</span><span class=\"p\">]</span>\n</code></pre></div></div>\n<h2 id=\"defining-some-useful-functions\">Defining some useful functions</h2>\n<p>Next, we need to define some useful functions for the active learning pipeline.</p>\n<p>The first function creates a virtual library by combining three sets of building blocks. The smi files used here were borrowed from <a href=\"https://github.com/PatWalters/TS\">Pat Walters repository on Thompson sampling</a>. The second creates the Morgan fingerprints for all compounds within the library for training the machine learning model, which is trained using the third function. The final function scores the entire library using the machine learning model.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">build_virtual_library</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Builds a virtual library by coupling building blocks from the input smi files.\n\n    Returns:\n        pd.DataFrame: A DataFrame containing the molecular objects and SMILES strings of\n        the products.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index_col</span><span class=\"o\">=</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">reaction_smarts</span> <span class=\"o\">=</span> <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n        <span class=\"n\">bb_types</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]</span>\n        <span class=\"n\">rxn</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span><span class=\"n\">reaction_smarts</span><span class=\"p\">)</span>\n\n        <span class=\"n\">building_blocks</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"n\">bb_types</span><span class=\"p\">:</span>\n            <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n                    <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n                    <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n            <span class=\"n\">building_blocks</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n\n        <span class=\"n\">total_prods</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"p\">.</span><span class=\"n\">prod</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">building_blocks</span><span class=\"p\">])</span>\n\n        <span class=\"n\">product_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">reagents</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">product</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">building_blocks</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">total_prods</span><span class=\"p\">):</span>\n            <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">reagents</span><span class=\"p\">]</span>\n            <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n            <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n                <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n                <span class=\"n\">product_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">product_list</span><span class=\"p\">,</span>\n            <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">m</span> <span class=\"ow\">in</span> <span class=\"n\">product_list</span><span class=\"p\">],</span>\n            <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">],</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">name</span> <span class=\"o\">=</span> <span class=\"s\">\"smiles\"</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">create_morgan_fingerprints</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Creates Morgan fingerprints for the input library.\n\n    Args:\n        library (pd.DataFrame): The input library.\n\n    Returns:\n        pd.DataFrame: The Morgan fingerprints of the input library.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/fingerprints.csv\"</span><span class=\"p\">).</span><span class=\"n\">set_index</span><span class=\"p\">(</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n            <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganFingerprintAsBitVect</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">,</span> <span class=\"n\">radius</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">))</span>\n            <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">)</span>\n        <span class=\"p\">]</span>\n        <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">fps</span><span class=\"p\">,</span> <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"fp_</span><span class=\"si\">{</span><span class=\"n\">x</span><span class=\"si\">}</span><span class=\"s\">\"</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">fps</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]))],</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/fingerprints.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">fps_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_ml_model</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Trains a random forest regressor model on slow scores for the input library.\n\n    Args:\n        library (pd.DataFrame): The input library.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n\n    Returns:\n        RandomForestRegressor: The trained random forest regressor model.\n    \"\"\"</span>\n    <span class=\"n\">scored</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"n\">isna</span><span class=\"p\">()]</span>\n    <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">fingerprints</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">scored</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">]</span>\n    <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">scored</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span>\n    <span class=\"n\">regressor</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">max_depth</span><span class=\"o\">=</span><span class=\"mi\">10</span><span class=\"p\">)</span>\n    <span class=\"n\">regressor</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">regressor</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">score_library</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">regressor</span><span class=\"p\">:</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">,</span>\n    <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Scores the entire library with the trained model.\n\n    Args:\n        library (pd.DataFrame): The input library.\n        regressor (RandomForestRegressor): The trained random forest regressor model.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n\n    Returns:\n        pd.DataFrame: The input library with the model scores.\n    \"\"\"</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">regressor</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n</code></pre></div></div>\n<h2 id=\"active-learning-pipeline\">Active learning pipeline</h2>\n<p>We then need to set up the active learning pipeline. This follows the steps from above:</p>\n<ol>\n<li>A random compound is selected as a reference compound.</li>\n<li>A random sample of the unlabelled data is selected and labelled using the expensive scoring function (Tanimoto similarity).</li>\n<li>These labelled datapoints are used to train a simple machine learning (random forest) regressor.</li>\n</ol>\n<p>Followed then by a repeating cycle:</p>\n<ol>\n<li>The regressor is used to score the entire library</li>\n<li>The compounds with the highest scores from the machine learning regressor are labelled using the expensive scoring function</li>\n<li>The labels from the expensive scoring function are pooled and the machine learning model is re-trained</li>\n</ol>\n<p>Because we sometimes know the best value for a scoring function, I've added an early stopping condition if the best compound is found to save time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_active_learning</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">compounds_per_round</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">number_of_rounds</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">scoring_function</span><span class=\"p\">:</span> <span class=\"nb\">callable</span><span class=\"p\">,</span>\n    <span class=\"n\">minimize</span><span class=\"p\">:</span> <span class=\"nb\">bool</span><span class=\"p\">,</span>\n    <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">|</span> <span class=\"bp\">None</span> <span class=\"o\">=</span> <span class=\"bp\">None</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Runs active learning on the virtual library.\n\n    Args:\n        library (pd.DataFrame): The input virtual library.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n        compounds_per_round (int): Number of compounds to select per round.\n        number_of_rounds (int): Number of active learning rounds.\n        scoring_function (callable): The slow scoring function.\n        minimize (bool): Whether to minimize or maximize the slow scoring function.\n        early_stopping_value (float | None): If the slow scoring function finds a\n        compound with this value, stops the active learning.\n\n    Returns:\n        pd.DataFrame: The input library with the slow scores added.\n    \"\"\"</span>\n    <span class=\"c1\"># Select initial random sample\n</span>    <span class=\"n\">initial_sample</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"n\">compounds_per_round</span><span class=\"p\">)</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Score the initial sample\n</span>    <span class=\"n\">initial_scores</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">initial_sample</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n\n    <span class=\"c1\"># Save the slow scores\n</span>    <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">initial_sample</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">initial_scores</span>\n\n    <span class=\"c1\"># Run active learning\n</span>    <span class=\"n\">al_round</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"k\">while</span> <span class=\"n\">al_round</span> <span class=\"o\">&lt;</span> <span class=\"n\">number_of_rounds</span><span class=\"p\">:</span>\n        <span class=\"c1\"># Train the ML model\n</span>        <span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">train_ml_model</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Use the model to score the entire virtual library\n</span>        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">score_library</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">,</span> <span class=\"n\">model</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Select the top scoring molecules with no slow scores\n</span>        <span class=\"n\">top_compounds</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n            <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"n\">isna</span><span class=\"p\">()]</span>\n            <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">minimize</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"n\">compounds_per_round</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Score the top molecules with the slow function\n</span>        <span class=\"n\">slow_scores</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n        <span class=\"c1\"># Save the slow scores\n</span>        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">slow_scores</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">al_round</span>\n\n        <span class=\"n\">al_round</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n        <span class=\"k\">if</span> <span class=\"n\">early_stopping_value</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n            <span class=\"k\">if</span> <span class=\"n\">minimize</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"nb\">min</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n                    <span class=\"k\">break</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"nb\">max</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n                    <span class=\"k\">break</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-1-finding-the-compound-with-the-lowest-clogp\">Example 1: Finding the compound with the lowest cLogP</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Partition_coefficient\">Calculated LogP (cLogP)</a> is an important property in drug discovery. Whilst we wouldn't usually be aiming to find the minimum or maximum value in a library, it's a useful example as it can be calculated quickly enough to exhaustively screen the library to confirm we're finding the best compound. In the example below, two compounds are randomly selected from the library and their Morgan fingerprints and cLogP values are used to train a machine learning model. This model scores the entire library and the cLogP for two best scoring compounds are pooled with the earlier compounds and the process repeated. After only six rounds (12 compounds scored), the active learning algorithm can find the lowest cLogP in the library, much quicker than exhaustively screening all 132,500 compounds! The experiment was repeated ten times and the number of compounded needed is constant across the repeats. cLogP is a simple property, but it's still impressive how little data the machine learning model needed to find the best scores.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">2</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">50</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Create the virtual library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">build_virtual_library</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># Create morgan fingerprints for the library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating Morgan fingerprints\"</span><span class=\"p\">)</span>\n<span class=\"n\">fingerprints</span> <span class=\"o\">=</span> <span class=\"n\">create_morgan_fingerprints</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Find the minimum logP in the library\n</span><span class=\"n\">all_clogp_values</span> <span class=\"o\">=</span> <span class=\"n\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Minimum logP in the library: </span><span class=\"si\">{</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">calc_logp</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">),</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">notna</span><span class=\"p\">()]))</span>\n\n<span class=\"n\">ave_scored_compounds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mean</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span> <span class=\"o\">-</span> <span class=\"n\">COMPOUNDS_PER_ROUND</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Average number of scored compounds before finding lowest value: </span><span class=\"si\">{</span><span class=\"n\">ave_scored_compounds</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Creating virtual library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1000000/1000000 [01:42&lt;00:00, 9747.08it/s]\n\nCreating Morgan fingerprints\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 132500/132500 [04:11&lt;00:00, 526.70it/s]\n\nMinimum logP in the library: -5.00\nRunning active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:21&lt;00:00,  2.11s/it]\n\nAverage number of scored compounds before finding lowest value: 12.00\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-2-recovering-a-random-compound-from-within-the-library-using-tanimoto-similarity\">Example 2: Recovering a random compound from within the library using Tanimoto similarity</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Chemical_similarity\">Molecular similarity</a> is often defined as the <a href=\"https://en.wikipedia.org/wiki/Jaccard_index\">Tanimoto similarity</a> between Morgan fingerprints. In the next experiment we'll try to maximise the Tanimoto similarity to a reference molecule. The reference molecule will be randomly selected from the library, so the maximum similarity will be 1. This turned out to be a significantly more difficult problem than the previous example minimizing cLogP. The number of compounds per round and number of rounded were increased (5000 compounds screened), and even still the reference molecule was only recovered once out of the ten repeats. This was a slightly surprising result considering the machine learning models were trained using the Morgan fingerprints that were also used in the Tanimoto similarity.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">comparison_smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">],</span> <span class=\"n\">ref_smiles</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculates the Tanimoto similarity of a reference compound to a list of\n    comparison compounds.\n\n    Args:\n        comparison_smiles (list[str]): List of SMILES to compare to\n        ref_smiles (str): SMILES of the reference compound\n\n    Returns:\n        list[float]: List of similarity scores\n    \"\"\"</span>\n    <span class=\"n\">ref_mol</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">ref_smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_mols</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_smiles</span><span class=\"p\">]</span>\n    <span class=\"n\">fpgen</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">()</span>\n    <span class=\"n\">ref_fp</span> <span class=\"o\">=</span> <span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">ref_mol</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_mols</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">DataStructs</span><span class=\"p\">.</span><span class=\"n\">FingerprintSimilarity</span><span class=\"p\">(</span><span class=\"n\">ref_fp</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_fps</span><span class=\"p\">]</span>\n\n\n<span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">50</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">False</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Choose a random reference molecule\n</span>    <span class=\"n\">ref_smiles</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">).</span><span class=\"n\">index</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">partial</span><span class=\"p\">(</span><span class=\"n\">calc_similarity</span><span class=\"p\">,</span> <span class=\"n\">ref_smiles</span><span class=\"o\">=</span><span class=\"n\">ref_smiles</span><span class=\"p\">),</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">top</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()].</span><span class=\"n\">sort_values</span><span class=\"p\">(</span>\n        <span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"round\"</span><span class=\"p\">:</span> <span class=\"n\">i</span><span class=\"p\">,</span>\n            <span class=\"s\">\"reference_molecule\"</span><span class=\"p\">:</span> <span class=\"n\">ref_smiles</span><span class=\"p\">,</span>\n            <span class=\"s\">\"compounds_scored\"</span><span class=\"p\">:</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()]),</span>\n            <span class=\"s\">\"top_scorer\"</span><span class=\"p\">:</span> <span class=\"n\">top</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">],</span>\n            <span class=\"s\">\"top_score\"</span><span class=\"p\">:</span> <span class=\"n\">top</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n\n<span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">).</span><span class=\"n\">set_index</span><span class=\"p\">(</span><span class=\"s\">\"round\"</span><span class=\"p\">).</span><span class=\"n\">style</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span>\n    <span class=\"p\">{</span><span class=\"s\">\"top_score\"</span><span class=\"p\">:</span> <span class=\"s\">\"{:.3f}\"</span><span class=\"p\">}</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Running active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [22:30&lt;00:00, 135.02s/it]\n</code></pre></div></div>\n<style type=\"text/css\">\n</style>\n<table id=\"T_5ea76\">\n<thead>\n<tr>\n<th class=\"blank level0\">Round</th>\n<th class=\"col_heading level0 col0\" id=\"T_5ea76_level0_col0\">Reference compound</th>\n<th class=\"col_heading level0 col1\" id=\"T_5ea76_level0_col1\">Compounds scored</th>\n<th class=\"col_heading level0 col3\" id=\"T_5ea76_level0_col3\">Top score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<th class=\"row_heading level0 row0\" id=\"T_5ea76_level0_row0\">0</th>\n<td class=\"data row0 col0\" id=\"T_5ea76_row0_col0\">COc1ccc2c(=O)n(C(=N)NCC(=O)O)c([C@@H](CN)CO)nc2c1OC</td>\n<td class=\"data row0 col1\" id=\"T_5ea76_row0_col1\">5100</td>\n<td class=\"data row0 col3\" id=\"T_5ea76_row0_col3\">0.466</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row1\" id=\"T_5ea76_level0_row1\">1</th>\n<td class=\"data row1 col0\" id=\"T_5ea76_row1_col0\">CC(=O)OC[C@H](C(=O)O)n1c([C@H](N)CN(C)C)nc2[nH][nH]c(=O)c2c1=O</td>\n<td class=\"data row1 col1\" id=\"T_5ea76_row1_col1\">3300</td>\n<td class=\"data row1 col3\" id=\"T_5ea76_row1_col3\">1.000</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row2\" id=\"T_5ea76_level0_row2\">2</th>\n<td class=\"data row2 col0\" id=\"T_5ea76_row2_col0\">Cc1cc2nc([C@H](C)NC(=O)CN)n(CC[C@H](N)C(=O)O)c(=O)c2cc1O</td>\n<td class=\"data row2 col1\" id=\"T_5ea76_row2_col1\">5100</td>\n<td class=\"data row2 col3\" id=\"T_5ea76_row2_col3\">0.506</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row3\" id=\"T_5ea76_level0_row3\">3</th>\n<td class=\"data row3 col0\" id=\"T_5ea76_row3_col0\">Nc1nncn1Cc1nc2c(cnc3ccnn32)c(=O)n1C(=O)[C@H](N)CC(=O)O</td>\n<td class=\"data row3 col1\" id=\"T_5ea76_row3_col1\">5100</td>\n<td class=\"data row3 col3\" id=\"T_5ea76_row3_col3\">0.495</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row4\" id=\"T_5ea76_level0_row4\">4</th>\n<td class=\"data row4 col0\" id=\"T_5ea76_row4_col0\">Cn1nnc2nc(C3(O)CC(N)C3)n([C@H](CO)C(=O)O)c(=O)c21</td>\n<td class=\"data row4 col1\" id=\"T_5ea76_row4_col1\">5100</td>\n<td class=\"data row4 col3\" id=\"T_5ea76_row4_col3\">0.488</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row5\" id=\"T_5ea76_level0_row5\">5</th>\n<td class=\"data row5 col0\" id=\"T_5ea76_row5_col0\">Cn1cc2c(=O)n(CC[C@@H](N)C(=O)O)c([C@H](CO)NC(N)=O)nc2n1</td>\n<td class=\"data row5 col1\" id=\"T_5ea76_row5_col1\">5100</td>\n<td class=\"data row5 col3\" id=\"T_5ea76_row5_col3\">0.518</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row6\" id=\"T_5ea76_level0_row6\">6</th>\n<td class=\"data row6 col0\" id=\"T_5ea76_row6_col0\">COC(=O)c1ccc2c(=O)n(CC[C@@H](N)C(=O)O)c([C@@H](N)CCO)nc2c1</td>\n<td class=\"data row6 col1\" id=\"T_5ea76_row6_col1\">5100</td>\n<td class=\"data row6 col3\" id=\"T_5ea76_row6_col3\">0.449</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row7\" id=\"T_5ea76_level0_row7\">7</th>\n<td class=\"data row7 col0\" id=\"T_5ea76_row7_col0\">C[C@H](O)[C@H](N)c1nc2cnc(Cl)nc2c(=O)n1[C@H](C(=O)O)[C@@H](C)O</td>\n<td class=\"data row7 col1\" id=\"T_5ea76_row7_col1\">5100</td>\n<td class=\"data row7 col3\" id=\"T_5ea76_row7_col3\">0.434</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row8\" id=\"T_5ea76_level0_row8\">8</th>\n<td class=\"data row8 col0\" id=\"T_5ea76_row8_col0\">Cn1cnc2c(=O)n(C(=O)C[C@H](N)C(=O)O)c([C@@H](N)CO)nc21</td>\n<td class=\"data row8 col1\" id=\"T_5ea76_row8_col1\">5100</td>\n<td class=\"data row8 col3\" id=\"T_5ea76_row8_col3\">0.519</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row9\" id=\"T_5ea76_level0_row9\">9</th>\n<td class=\"data row9 col0\" id=\"T_5ea76_row9_col0\">C[C@@H](O)[C@H](N)c1nc2nc(N)ncc2c(=O)n1C(=O)NCC(=O)O</td>\n<td class=\"data row9 col1\" id=\"T_5ea76_row9_col1\">5100</td>\n<td class=\"data row9 col3\" id=\"T_5ea76_row9_col3\">0.458</td>\n</tr>\n</tbody>\n</table>\n<p>Looking at the final run, we can see that in general the maximum similarity is not increasing significantly with further rounds, apart from the occasional spike as the model finds a similar compound. This suggests that increasing the number of rounds may not increase the performance.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">lineplot</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">[[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"s\">\"scored_round\"</span><span class=\"p\">]].</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">).</span><span class=\"nb\">max</span><span class=\"p\">().</span><span class=\"n\">values</span>\n<span class=\"p\">).</span><span class=\"nb\">set</span><span class=\"p\">(</span><span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Round\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Max similarity to reference compound\"</span><span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot of max similarity across active learning rounds\" src=\"https://jonswain.github.io/images/active_learning/active_learning_13_0.png\"/>\n</p>\n<h2 id=\"why-does-al-work-well-for-clogp-but-not-for-tanimoto-similarity\">Why does AL work well for cLogP but not for Tanimoto similarity?</h2>\n<p>AL was very successful at finding the lowest cLogP value in a library but struggled to recover a randomly chosen reference molecule by trying to maximize the Tanimoto similarity. The reason behind this may be due to the underlying distribution of data within the library. By plotting the cLogP values of all compounds within the library, we can see that the data is close to being normally distributed. The machine learning model is able to learn to predict cLogP well from the data it is given. For the Tanimoto similarity, the scores for the whole library of similarity to the reference compound from the final run are plotted, which have a large positive skew. There is lots of data for predicting Tanimoto similarities between 0.1 and 0.4, but very little data for scores above 0.4. So even though the machine learning model is using the same representation as is used to calculate the Tanimoto similarity (Morgan fingerprint), it doesn't have enough information to learn what makes a high score.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">all_similarities</span> <span class=\"o\">=</span> <span class=\"n\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">(),</span> <span class=\"n\">ref_smiles</span><span class=\"p\">)</span>\n<span class=\"n\">fig</span><span class=\"p\">,</span> <span class=\"n\">ax</span> <span class=\"o\">=</span> <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">subplots</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"n\">figsize</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">12</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">))</span>\n<span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">histplot</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">,</span> <span class=\"n\">bins</span><span class=\"o\">=</span><span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span> <span class=\"n\">label</span><span class=\"o\">=</span><span class=\"s\">\"True scores\"</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n<span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"nb\">set</span><span class=\"p\">(</span><span class=\"n\">title</span><span class=\"o\">=</span><span class=\"s\">\"cLogP values for library\"</span><span class=\"p\">,</span> <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"cLogP\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Frequency\"</span><span class=\"p\">)</span>\n<span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">histplot</span><span class=\"p\">(</span><span class=\"n\">all_similarities</span><span class=\"p\">,</span> <span class=\"n\">bins</span><span class=\"o\">=</span><span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span> <span class=\"n\">label</span><span class=\"o\">=</span><span class=\"s\">\"Predicted scores\"</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">])</span>\n<span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">].</span><span class=\"nb\">set</span><span class=\"p\">(</span>\n    <span class=\"n\">title</span><span class=\"o\">=</span><span class=\"s\">\"Tanimoto similarity to reference compound\"</span><span class=\"p\">,</span>\n    <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Similarity\"</span><span class=\"p\">,</span>\n    <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Frequency\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot score distributions for different tasks\" src=\"https://jonswain.github.io/images/active_learning/active_learning_15_0.png\"/>\n</p>\n<h2 id=\"example-3-docking-using-smina\">Example 3: Docking using SMINA</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Docking_(molecular)\">Molecular docking</a> is a common technique used in drug discovery to predict how a small molecule ligand will bind to a protein target. This can be combined with a scoring function to estimate the binding affinity, and these scores can be used to rank virtual compounds for experimental validation. I am far from an expert on molecular docking, but here we'll use it as an example of how active learning could be used in a real drug discovery project.</p>\n<p>Docking is slow. In this example we'll use <a href=\"https://sourceforge.net/projects/smina/\">SMINA</a>, which seem to take around one minute for each compound. We're docking compounds to the SARS MPro protein and using active learning to try find the highest binding affinity by minimizing the predicted binding energy. At one minute per docking calculation, it would take 13 weeks to exhaustively screen the virtual library used here.</p>\n<p>The experiment was repeated three times, twice it improved on the docking scores of the initial random sample, and on one run it found a very good scoring compound in the initial random sample and wasn't able to improve on that. Due to running this on my laptop, I kept the number of compounds per round (five) and the number of rounds (10) low. With more computing power, it would be interesting to see how the performance of the active learning improves with increasing these values.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">dock_mols</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Dock the molecules to the SARS MPro protein and return the affinity of the best\n    pose for each.\n\n    Args:\n        smiles: list[str]: The molecules to dock\n\n    Returns:\n        list[float]: The affinities of the best pose for each input molecule\n    \"\"\"</span>\n    <span class=\"n\">mols</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">smiles</span><span class=\"p\">]</span>\n    <span class=\"n\">scores</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n\n    <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">mols</span><span class=\"p\">:</span>\n        <span class=\"k\">with</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SDWriter</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf.sdf\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">w</span><span class=\"p\">:</span>\n            <span class=\"n\">m</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">AddHs</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span>\n            <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">EmbedMultipleConfs</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">,</span> <span class=\"n\">numConfs</span><span class=\"o\">=</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"n\">numThreads</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n            <span class=\"n\">confs</span> <span class=\"o\">=</span> <span class=\"n\">m</span><span class=\"p\">.</span><span class=\"n\">GetConformers</span><span class=\"p\">()</span>\n            <span class=\"k\">for</span> <span class=\"n\">c</span> <span class=\"ow\">in</span> <span class=\"n\">confs</span><span class=\"p\">:</span>\n                <span class=\"n\">w</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">,</span> <span class=\"n\">confId</span><span class=\"o\">=</span><span class=\"n\">c</span><span class=\"p\">.</span><span class=\"n\">GetId</span><span class=\"p\">())</span>\n\n        <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n            <span class=\"p\">[</span>\n                <span class=\"s\">\"./smina.osx.12\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--exhaustiveness\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"10\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--cpu\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"10\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--seed\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"0\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--autobox_ligand\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/ligand_only.pdb\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-r\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/protein_minus_ligand.pdb\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-l\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/tmp_conf.sdf\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-o\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/tmp_conf_docked.sdf.gz\"</span><span class=\"p\">,</span>\n            <span class=\"p\">],</span>\n            <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n            <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"k\">with</span> <span class=\"n\">gzip</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf.gz\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f_in</span><span class=\"p\">:</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf\"</span><span class=\"p\">,</span> <span class=\"s\">\"wb\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f_out</span><span class=\"p\">:</span>\n                <span class=\"n\">shutil</span><span class=\"p\">.</span><span class=\"n\">copyfileobj</span><span class=\"p\">(</span><span class=\"n\">f_in</span><span class=\"p\">,</span> <span class=\"n\">f_out</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># The output sdf wasn't loading so this is a workaround\n</span>        <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf\"</span><span class=\"p\">,</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n            <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">read</span><span class=\"p\">()</span>\n        <span class=\"n\">affinities</span> <span class=\"o\">=</span> <span class=\"n\">re</span><span class=\"p\">.</span><span class=\"n\">findall</span><span class=\"p\">(</span><span class=\"sa\">r</span><span class=\"s\">\"<minimizedaffinity>\\n(-\\d.\\d+)\"</minimizedaffinity></span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n        <span class=\"n\">affinities</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">affinities</span><span class=\"p\">]</span>\n\n        <span class=\"n\">scores</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">affinities</span><span class=\"p\">))</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">scores</span>\n\n\n<span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">3</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">cumulative_scores</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">dock_mols</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">top</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()].</span><span class=\"n\">sort_values</span><span class=\"p\">(</span>\n        <span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"nb\">min</span><span class=\"p\">())</span>\n    <span class=\"n\">cumulative_scores</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"run_</span><span class=\"si\">{</span><span class=\"n\">i</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">scored_round</span> <span class=\"o\">&lt;=</span> <span class=\"n\">j</span><span class=\"p\">].</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"nb\">min</span><span class=\"p\">()</span>\n        <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">)</span>\n    <span class=\"p\">]</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Average lowest docking score after screening </span><span class=\"si\">{</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">*</span> <span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"si\">}</span><span class=\"s\"> compounds: </span><span class=\"si\">{</span><span class=\"nb\">sum</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Plot the cumulative best score found in each round for the final run\n</span><span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">lineplot</span><span class=\"p\">(</span><span class=\"n\">cumulative_scores</span><span class=\"p\">).</span><span class=\"nb\">set</span><span class=\"p\">(</span>\n    <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Round\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Minimum docking score\"</span><span class=\"p\">,</span> <span class=\"n\">xticks</span><span class=\"o\">=</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">)</span>\n<span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Running active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [4:54:58&lt;00:00, 5899.39s/it]  \n\nAverage lowest docking score after screening 50 compounds: -9.14\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot score against round for across three different active learning runs\" src=\"https://jonswain.github.io/images/active_learning/active_learning_17_4.png\"/>\n</p>\n<h2 id=\"summary\">Summary</h2>\n<p>In this post we've discussed active learning and seen three examples of how it could be used when our virtual library has grown too large to exhaustively screen every compound.</p>\n<h3 id=\"advantages-of-al\">Advantages of AL</h3>\n<ul>\n<li>It's much faster than exhaustive searching, and often returns the best result from within the library.</li>\n<li>Can be used to minimize or maximize a scoring function.</li>\n</ul>\n<h3 id=\"disadvantages\">Disadvantages</h3>\n<ul>\n<li>It requires a fully enumerated library. This isn't an issue for the dataset used here, but for ultra-large combinatorial libraries, it will take a long time and require a lot of memory to enumerate and store all the compounds.</li>\n<li>For ultra-large libraries scoring the entire library with the machine learning model may be too expensive.</li>\n</ul>","doi":"https://doi.org/10.59350/spfma-4yc10","guid":"https://jonswain.github.io/ultra-large-libraries-part-1","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1715990400,"rid":"td357-5y165","summary":"This is part 1 of a planned three post series on working with large chemical libraries. The notebook used to create this post and all the files can be found in this github repo.","tags":["Active-learning","Ai","Cheminformatics","Data-science","Machine-learning"],"title":"Working with Large Virtual Chemical Libraries: Part 1 - Active Learning","updated_at":1788763537,"url":"https://jonswain.github.io/ultra-large-libraries-part-1/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>I've tried to visualise and compare distributions using violin plots for reports and presentations in the past, and the feedback I've got was generally\u2026 not great. When searching for better methods I came across <a href=\"https://medium.com/@alexbelengeanu/getting-started-with-raincloud-plots-in-python-2ea5c2d01c11\">this excellent blog post by Alex Belengeanu</a> on raincloud plots and I'm now a big fan.</p>\n<hr/>\n<h2 id=\"plotting-complex-distributions\">Plotting complex distributions</h2>\n<p>There's lots of methods for visualising distributions to compare different populations, and each has its own pros and cons. Histograms (often with a kernel density estimation) are good method for showing complex distributions, but it's not possible to easily compare statistics such as the mean or quantiles from the raw graph, and there's also some loss of information from binning or smoothing, which could misrepresent the data, such as making discrete values appear continuous. Box and whisker plots are great for being able to compare statistics such as means and quartiles, but they don't represent complex distributions such as bimodal data well. A jitter plot is good for showing the structure of the underlying data, but difficult to compare statistics or absolute densities of points.</p>\n<p>A violin plot with box plot combines the strengths of the two methods to visualise complex distributions with easy to read off statistics. The main downside is they're ugly (IMO). Every time I've used them in a report or presentation the discussion has veered off to whether they look more like sea creatures or medieval weapons than violins, with very little discussion of the actual data!</p>\n<p>Whilst searching for better methods I came across <a href=\"https://medium.com/@alexbelengeanu/getting-started-with-raincloud-plots-in-python-2ea5c2d01c11\">this blog post by Alex Belengeanu</a> on raincloud plots, which seem to be the best alternative.</p>\n<h2 id=\"raincloud-plots\">Raincloud plots</h2>\n<p>Raincloud plots are pretty simple, they're just a combination of half a violin plot (sometimes called a ridgeline plot), a box plot, and a jitter plot. They're intuitive to read and show distributions clearly, as well as being much more aesthetically pleasing than many alternatives. I've customised Alex Belengeanu's code to suit my needs, and it's all in a function in <a href=\"https://github.com/jonswain/raincloudplots/\">this GitHub repo</a>.</p>\n<p><img alt=\"A raincloud plot of the features in the sklearn petal dataset\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/raincloudplot/petals.png\"/></p>","doi":"https://doi.org/10.59350/0sww0-n3193","guid":"https://jonswain.github.io/Displaying-distributions-with-raincloud-plots","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1730419200,"rid":"e3ae3-xpz23","summary":"I've tried to visualise and compare distributions using violin plots for reports and presentations in the past, and the feedback I've got was generally\u2026 not great. When searching for better methods I came across this excellent blog post by Alex Belengeanu on raincloud plots and I'm now a big fan.","tags":["Data-science","Visualisation"],"title":"Displaying Distributions with Raincloud Plots","updated_at":1788763532,"url":"https://jonswain.github.io/displaying-distributions-with-raincloud-plots/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>This is part 2 of a a planned three post series on working with large chemical libraries.\nThe notebook used to create this post, and all the files can be found in <a href=\"https://github.com/jonswain/ga-for-ul-libraries\">this github repo</a>.</p>\n<hr/>\n<h2 id=\"combinatorial-libraries\">Combinatorial libraries</h2>\n<p>Combinatorial libraries can grow quickly, combining three sets of 1,000 building blocks allows you to access one billion possibilities. For example, 1,000 amines, 1,000 halide functionalised carboxylic acids, and 1,000 boronic acids can be combined to make a billion-member library. That would be a lot of synthesis and a lot of testing if you wanted to screen every member of the library! Even if a computational method took 1 second to score each virtual compound, it would take 32 years to score the entire library. Make-on-demand chemical suppliers such as Enamine have libraries that contain multiple billions of compounds, often created by combining sets of building blocks using reliable chemistry. We need methods for effectively sampling this chemical space.</p>\n<p>In this post I'll be looking into using a genetic algorithm to search a small combinatorial library. These are quick and incredibly simple methods that can be used to search combinatorial libraries for the combination of building blocks that maximises or minimises a scoring function.</p>\n<p>Whilst this method uses a combinatorial library that contains all possible configurations of all three building blocks, it's possible to extend it to more complex combinatorial libraries by <a href=\"https://www.youtube.com/watch?v=lNzW6_z_jko\">storing the building blocks and reactions as a graph</a>.</p>\n<h2 id=\"genetic-algorithms\">Genetic algorithms</h2>\n<p>Genetic algorithms are biologically inspired, based on biological natural selection. They use a very simple algorithm, with no machine learning or complex statistics. One of the big advantages is that you don't need to fully enumerate of score the entire library, only certain combinations of building blocks need to be enumerated and scores, reducing memory and computational costs. Working in building block space rather than with enumerated structures means the complexity will scale with number of building blocks, which will increase much more slowly than the total library size. The downside of the genetic algorithm is that it requires combinatorial libraries and won't work with large collections of diverse molecules not made up from the same building blocks.</p>\n<p>Initially a random population of the unlabelled data is selected by randomly choosing building blocks and enumerating the reaction products from these building blocks.</p>\n<p>The genetic algorithm has a cycle that is repeated, and each round is called a generation.</p>\n<ol>\n<li>The population is labelled using the expensive scoring function.</li>\n<li>A selection pressure is applied to the population (the lowest scoring compounds in the population are removed).</li>\n<li>A new population is created by randomly shuffling the building blocks from the surviving population (mating).</li>\n<li>Mutations (random building blocks) are added to the population to prevent getting stuck in a local minimum.</li>\n<li>The above steps are repeated on the new population until a finish criterion is met.</li>\n</ol>\n<hr/>\n<h2 id=\"imports\">Imports</h2>\n<p>First, we need to import the libraries we will be using.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">math</span>\n<span class=\"kn\">import</span> <span class=\"nn\">time</span>\n<span class=\"kn\">from</span> <span class=\"nn\">functools</span> <span class=\"kn\">import</span> <span class=\"n\">partial</span>\n<span class=\"kn\">from</span> <span class=\"nn\">itertools</span> <span class=\"kn\">import</span> <span class=\"n\">product</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n<span class=\"kn\">from</span> <span class=\"nn\">typing</span> <span class=\"kn\">import</span> <span class=\"n\">Callable</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">AllChem</span><span class=\"p\">,</span> <span class=\"n\">DataStructs</span><span class=\"p\">,</span> <span class=\"n\">Descriptors</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n</code></pre></div></div>\n<h2 id=\"expensive-scoring-function\">Expensive scoring function</h2>\n<p>The first function we will define is the expensive scoring function, this will take a list of RDKit molecules and return a list of scores. As in the previous example, I'm going to try find the compound from within the library with the lowest calculated Log P (cLogP). This is actually a very fast calculation and can be done exhaustively, which means we can confirm if the genetic algorithm is finding the lowest value and triggering the early stopping.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">mols</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculate the logP value for a list of compounds.\n\n    Args:\n        mols (list[Chem.Mol]): The molecules.\n\n    Returns:\n        list[float]: The scores of the molecules.\n    \"\"\"</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">mols</span><span class=\"p\">]</span>\n</code></pre></div></div>\n<h2 id=\"defining-some-useful-functions\">Defining some useful functions</h2>\n<p>Next, we need to define some useful functions for the active learning pipeline.</p>\n<p>The first function takes the indices of three building blocks and enumerates the product of their reaction. Since not all combinations are possible, it will return None if the reaction fails. The second function takes the surviving population from the selection pressure and creates a new population by shuffling the building blocks. The third takes the new shuffled population and adds in random mutations, this prevents the algorithm getting stuck in a local minimum by ensuring building block diversity.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_molecule</span><span class=\"p\">(</span>\n    <span class=\"n\">r1</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">r2</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">r3</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]],</span>\n    <span class=\"n\">rxn</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">rdChemReactions</span><span class=\"p\">.</span><span class=\"n\">ChemicalReaction</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"React building blocks to make a molecule.\n\n    Args:\n        r1 (int): Index of the first building block.\n        r2 (int): Index of the second building block.\n        r3 (int): Index of the third building block.\n        building_blocks (list[list[str]]): The building blocks.\n        rxn (Chem.rdChemReactions.ChemicalReaction): The reaction to combine the\n                                                     building blocks.\n\n\n    Returns:\n        Chem.Mol | None: The molecule or None if the reaction fails.\n    \"\"\"</span>\n    <span class=\"n\">bbs</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"n\">r1</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">][</span><span class=\"n\">r2</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">][</span><span class=\"n\">r3</span><span class=\"p\">]]</span>\n    <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">bbs</span><span class=\"p\">]</span>\n    <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n        <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n        <span class=\"k\">return</span> <span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"bp\">None</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">shuffle_population</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">population_size</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Shuffle the population building blocks.\n\n    Args:\n        data (pd.DataFrame): The surviving population data.\n        population_size (int): The size of the population.\n\n    Returns:\n        pd.DataFrame: The shuffled population.\n    \"\"\"</span>\n    <span class=\"n\">r1s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r1\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">r2s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r2\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">r3s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r3\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">new_population</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"r1\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r1s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n            <span class=\"s\">\"r2\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r2s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n            <span class=\"s\">\"r3\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r3s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">new_population</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">mutate_population</span><span class=\"p\">(</span>\n    <span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">mutation_rate</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]]</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Mutate the population building blocks.\n\n    Args:\n        data (pd.DataFrame): The surviving population data.\n        mutation_rate (float): The fraction of building blocks to mutate.\n        building_blocks (list[list[str]]): The building blocks.\n\n    Returns:\n        pd.DataFrame: The mutated population.\n    \"\"\"</span>\n    <span class=\"k\">for</span> <span class=\"n\">count</span><span class=\"p\">,</span> <span class=\"n\">column</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">columns</span><span class=\"p\">):</span>\n        <span class=\"n\">selection</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"n\">frac</span><span class=\"o\">=</span><span class=\"n\">mutation_rate</span><span class=\"p\">)</span>\n        <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">selection</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span>\n            <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"n\">count</span><span class=\"p\">]),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">selection</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">data</span>\n</code></pre></div></div>\n<h2 id=\"genetic-algorithm-pipeline\">Genetic algorithm pipeline</h2>\n<p>The genetic algorithm needs the lists of building blocks, a reaction to couple them to enumerate products, and a function to score the molecules. It also has a number of hyperparameters than can be tuned:</p>\n<ul>\n<li><code class=\"language-plaintext highlighter-rouge\">population_size</code>: The number of molecules in each generation to enumerate. A larger population will have a greater diversity of building blocks but will take longer to enumerate and score.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">num_generations</code>: The number of cycles of the genetic algorithm to run. More generations will give a better chance of finding the optimum value but will take longer to run.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">selection_pressure</code>: The fraction of the population to remove each cycle. A stronger selection pressure will speed up selection but increase the chance of being caught in a local minimum.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">mutation_rate</code>: The proportion of building blocks in the new population to replace with a random building block. A too high value will stop the algorithm finding the best combination, but a too low value will also increase the chance of being caught in a local minimum.</li>\n</ul>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_genetic_algorithm</span><span class=\"p\">(</span>\n    <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]],</span>\n    <span class=\"n\">rnx</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">rdChemReactions</span><span class=\"p\">.</span><span class=\"n\">ChemicalReaction</span><span class=\"p\">,</span>\n    <span class=\"n\">population_size</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">num_generations</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">selection_pressure</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span>\n    <span class=\"n\">mutation_rate</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span>\n    <span class=\"n\">scoring_function</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">,</span>\n    <span class=\"n\">minimize</span><span class=\"p\">:</span> <span class=\"nb\">bool</span><span class=\"p\">,</span>\n    <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">|</span> <span class=\"bp\">None</span> <span class=\"o\">=</span> <span class=\"bp\">None</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run the genetic algorithm.\n\n    Args:\n        building_blocks (list[list[str]]): The building blocks.\n        rnx (Chem.rdChemReactions.ChemicalReaction): The reaction to combine the\n                                                     building blocks.\n        population_size (int): The size of the population in each generation.\n        num_generations (int): The number of generations to run.\n        selection_pressure (float): The fraction of the population to discard.\n        mutation_rate (float): The fraction of the population to mutate.\n        scoring_function (Callable): The function to score the molecules.\n        minimize (bool): Whether to minimize the scoring function.\n        early_stopping_value (float | None, optional): The value to stop early at.\n                                                       Defaults to None.\n\n    Returns:\n        pd.DataFrame: The history of the population.\n    \"\"\"</span>\n    <span class=\"c1\"># To keep track of the population each generation\n</span>    <span class=\"n\">history</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">()</span>\n\n    <span class=\"c1\"># Choose initial population\n</span>    <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"r1\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n            <span class=\"s\">\"r2\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n            <span class=\"s\">\"r3\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"k\">for</span> <span class=\"n\">generation</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_generations</span><span class=\"p\">):</span>\n        <span class=\"c1\"># Generate molecules\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"nb\">apply</span><span class=\"p\">(</span>\n            <span class=\"k\">lambda</span> <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">make_molecule</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r1\"</span><span class=\"p\">],</span> <span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r2\"</span><span class=\"p\">],</span> <span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r3\"</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">,</span> <span class=\"n\">rnx</span><span class=\"p\">),</span>\n            <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Kill off fatal mutants\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">().</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Score molecules\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n\n        <span class=\"c1\"># Save population for analysis\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"generation\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">generation</span>\n        <span class=\"n\">history</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">([</span><span class=\"n\">history</span><span class=\"p\">,</span> <span class=\"n\">population</span><span class=\"p\">])</span>\n\n        <span class=\"c1\"># Early stopping\n</span>        <span class=\"k\">if</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n            <span class=\"k\">if</span> <span class=\"n\">minimize</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"nb\">round</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">].</span><span class=\"nb\">min</span><span class=\"p\">(),</span> <span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"o\">&lt;=</span> <span class=\"nb\">round</span><span class=\"p\">(</span>\n                    <span class=\"n\">early_stopping_value</span><span class=\"p\">,</span> <span class=\"mi\">5</span>\n                <span class=\"p\">):</span>\n                    <span class=\"k\">break</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"nb\">round</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">].</span><span class=\"nb\">max</span><span class=\"p\">(),</span> <span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"o\">&gt;=</span> <span class=\"nb\">round</span><span class=\"p\">(</span>\n                    <span class=\"n\">early_stopping_value</span><span class=\"p\">,</span> <span class=\"mi\">5</span>\n                <span class=\"p\">):</span>\n                    <span class=\"k\">break</span>\n\n        <span class=\"c1\"># Select top performing molecules\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n            <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"score\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">minimize</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">population_size</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\">-</span> <span class=\"n\">selection_pressure</span><span class=\"p\">)))</span>\n            <span class=\"p\">.</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Shuffle and mutate\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">shuffle_population</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">,</span> <span class=\"n\">population_size</span><span class=\"p\">)</span>\n        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">mutate_population</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">,</span> <span class=\"n\">mutation_rate</span><span class=\"p\">,</span> <span class=\"n\">building_blocks</span><span class=\"p\">)</span>\n        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">drop_duplicates</span><span class=\"p\">().</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">history</span>\n</code></pre></div></div>\n<p>This function fully enumerates a virtual library by combining three sets of building blocks. The smi files used here were borrowed from <a href=\"https://github.com/PatWalters/TS\">Pat Walters repository on Thompson sampling</a>. This is not necessary for the genetic algorithm but will allow us to see if the genetic algorithm if finding the best values.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">build_virtual_library</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Build a virtual library by coupling building blocks from the input smi files.\n\n    Returns:\n        pd.DataFrame: The virtual library.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index_col</span><span class=\"o\">=</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">reaction_smarts</span> <span class=\"o\">=</span> <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n        <span class=\"n\">bb_types</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]</span>\n        <span class=\"n\">rxn</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span><span class=\"n\">reaction_smarts</span><span class=\"p\">)</span>\n\n        <span class=\"n\">building_blocks</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"n\">bb_types</span><span class=\"p\">:</span>\n            <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n                    <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n                    <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n            <span class=\"n\">building_blocks</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n\n        <span class=\"n\">total_prods</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"p\">.</span><span class=\"n\">prod</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">building_blocks</span><span class=\"p\">])</span>\n\n        <span class=\"n\">product_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">reagents</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">product</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">building_blocks</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">total_prods</span><span class=\"p\">):</span>\n            <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">reagents</span><span class=\"p\">]</span>\n            <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n            <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n                <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n                <span class=\"n\">product_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">product_list</span><span class=\"p\">,</span>\n            <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">m</span> <span class=\"ow\">in</span> <span class=\"n\">product_list</span><span class=\"p\">],</span>\n            <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">],</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n\n\n<span class=\"c1\"># Chemistry parameters\n</span><span class=\"n\">RXN</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span>\n    <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">BUILDING_BLOCKS</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]:</span>\n    <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n    <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n        <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n            <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n            <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-1-finding-the-compound-with-the-lowest-clogp\">Example 1: Finding the compound with the lowest cLogP</h2>\n<p>The genetic algorithm was used to find the lowest cLogP from within the library. This was repeated 10 times with no tuning of the hyperparameters which may improve performance. The genetic algorithm reliably finds the combination of building blocks with the lowest cLogP in the combinatorial library, nearly 100 times faster than enumerating the entire library. On average the genetic algorithm only had to enumerate and score 600 combinations before it found the best scoring combination.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># GA parameters\n</span><span class=\"n\">POPULATION_SIZE</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">NUM_GENERATIONS</span> <span class=\"o\">=</span> <span class=\"mi\">20</span>\n<span class=\"n\">SELECTION_PRESSURE</span> <span class=\"o\">=</span> <span class=\"mf\">0.5</span>\n<span class=\"n\">MUTATION_RATE</span> <span class=\"o\">=</span> <span class=\"mf\">0.1</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Create the virtual library\n</span><span class=\"n\">virtual_library_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">build_virtual_library</span><span class=\"p\">()</span>\n<span class=\"n\">virtual_library_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Virtual library created in </span><span class=\"si\">{</span><span class=\"n\">virtual_library_end</span> <span class=\"o\">-</span> <span class=\"n\">virtual_library_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Find the minimum logP in the library\n</span><span class=\"n\">logp_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Calculating logP values for the library\"</span><span class=\"p\">)</span>\n<span class=\"n\">all_clogp_values</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n<span class=\"n\">logp_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Minimum logP in the library: </span><span class=\"si\">{</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"LogP calculations took </span><span class=\"si\">{</span><span class=\"n\">logp_end</span> <span class=\"o\">-</span> <span class=\"n\">logp_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Run the genetic algorithm\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Running genetic algorithm </span><span class=\"si\">{</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"si\">}</span><span class=\"s\"> times\"</span><span class=\"p\">)</span>\n<span class=\"n\">ga_times</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">max_generations</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"n\">ga_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">logp_ga_run</span> <span class=\"o\">=</span> <span class=\"n\">run_genetic_algorithm</span><span class=\"p\">(</span>\n        <span class=\"n\">building_blocks</span><span class=\"o\">=</span><span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">,</span>\n        <span class=\"n\">rnx</span><span class=\"o\">=</span><span class=\"n\">RXN</span><span class=\"p\">,</span>\n        <span class=\"n\">population_size</span><span class=\"o\">=</span><span class=\"n\">POPULATION_SIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">num_generations</span><span class=\"o\">=</span><span class=\"n\">NUM_GENERATIONS</span><span class=\"p\">,</span>\n        <span class=\"n\">selection_pressure</span><span class=\"o\">=</span><span class=\"n\">SELECTION_PRESSURE</span><span class=\"p\">,</span>\n        <span class=\"n\">mutation_rate</span><span class=\"o\">=</span><span class=\"n\">MUTATION_RATE</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">calc_logp</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">),</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ga_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">ga_times</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span><span class=\"p\">)</span>\n    <span class=\"n\">max_generations</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">logp_ga_run</span><span class=\"p\">.</span><span class=\"n\">generation</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Genetic algorithm run took </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds on average, with a maximum of </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">max_generations</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> generations\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Calculate improvement\n</span><span class=\"n\">ga_time</span> <span class=\"o\">=</span> <span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span>\n<span class=\"n\">virtual_library_time</span> <span class=\"o\">=</span> <span class=\"n\">virtual_library_end</span> <span class=\"o\">-</span> <span class=\"n\">virtual_library_start</span>\n<span class=\"n\">logp_time</span> <span class=\"o\">=</span> <span class=\"n\">logp_end</span> <span class=\"o\">-</span> <span class=\"n\">logp_start</span>\n<span class=\"n\">improvement</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">virtual_library_time</span> <span class=\"o\">+</span> <span class=\"n\">logp_time</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"n\">ga_time</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"GA was </span><span class=\"si\">{</span><span class=\"n\">improvement</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> times faster than building and scoring the virtual library\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Creating virtual library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1000000/1000000 [01:48&lt;00:00, 9183.87it/s]\nVirtual library created in 120.60 seconds\n\nCalculating logP values for the library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 132500/132500 [00:27&lt;00:00, 4803.96it/s]\nMinimum logP in the library: -5.00\nLogP calculations took 27.59 seconds\n\nRunning genetic algorithm 10 times\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:14&lt;00:00,  1.41s/it]\n\nGenetic algorithm run took 1.41 seconds on average, with a maximum of 12 generations\nGA was 81.90 times faster than building and scoring the virtual library\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-2-recovering-a-random-compound-from-within-the-library-using-tanimoto-similarity\">Example 2: Recovering a random compound from within the library using Tanimoto similarity</h2>\n<p>A reference molecule was randomly selected from the library, and the genetic algorithm was used to recover it from the library by maximising the Tanimoto similarity. In the active learning experiments, this was failing, possibly due to the low number of compounds from within the library with high Tanimoto similarities. The genetic algorithm was able to recover the reference molecule every time, taking less than a second to do so each time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">comparison_mols</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">],</span> <span class=\"n\">ref_mol</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculate the Tanimoto similarity to a reference compound.\n\n    Args:\n        comparison_mols (list[Chem.Mol]): List of molecules to compare.\n        ref_mol (Chem.Mol): The reference molecule.\n\n    Returns:\n        list[float]: List of similarity scores\n    \"\"\"</span>\n    <span class=\"n\">fpgen</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">()</span>\n    <span class=\"n\">ref_fp</span> <span class=\"o\">=</span> <span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">ref_mol</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_mols</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">DataStructs</span><span class=\"p\">.</span><span class=\"n\">FingerprintSimilarity</span><span class=\"p\">(</span><span class=\"n\">ref_fp</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_fps</span><span class=\"p\">]</span>\n\n\n<span class=\"c1\"># Score the virtual library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Scoring the virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">tanimoto_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"n\">all_tanimoto_values</span> <span class=\"o\">=</span> <span class=\"n\">calc_similarity</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">(),</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">().</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n<span class=\"p\">)</span>\n<span class=\"n\">tanimoto_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Tanimoto calculations took </span><span class=\"si\">{</span><span class=\"n\">tanimoto_end</span> <span class=\"o\">-</span> <span class=\"n\">tanimoto_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># GA parameters\n</span><span class=\"n\">POPULATION_SIZE</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">NUM_GENERATIONS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">SELECTION_PRESSURE</span> <span class=\"o\">=</span> <span class=\"mf\">0.5</span>\n<span class=\"n\">MUTATION_RATE</span> <span class=\"o\">=</span> <span class=\"mf\">0.1</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">False</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Run the genetic algorithm\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Running genetic algorithm </span><span class=\"si\">{</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"si\">}</span><span class=\"s\"> times\"</span><span class=\"p\">)</span>\n<span class=\"n\">ga_times</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">max_generations</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Choose random reference molecule\n</span>    <span class=\"n\">ref_mol</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">().</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n\n    <span class=\"c1\"># Run the genetic algorithm\n</span>    <span class=\"n\">ga_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">tanimoto_ga_run</span> <span class=\"o\">=</span> <span class=\"n\">run_genetic_algorithm</span><span class=\"p\">(</span>\n        <span class=\"n\">building_blocks</span><span class=\"o\">=</span><span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">,</span>\n        <span class=\"n\">rnx</span><span class=\"o\">=</span><span class=\"n\">RXN</span><span class=\"p\">,</span>\n        <span class=\"n\">population_size</span><span class=\"o\">=</span><span class=\"n\">POPULATION_SIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">num_generations</span><span class=\"o\">=</span><span class=\"n\">NUM_GENERATIONS</span><span class=\"p\">,</span>\n        <span class=\"n\">selection_pressure</span><span class=\"o\">=</span><span class=\"n\">SELECTION_PRESSURE</span><span class=\"p\">,</span>\n        <span class=\"n\">mutation_rate</span><span class=\"o\">=</span><span class=\"n\">MUTATION_RATE</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">partial</span><span class=\"p\">(</span><span class=\"n\">calc_similarity</span><span class=\"p\">,</span> <span class=\"n\">ref_mol</span><span class=\"o\">=</span><span class=\"n\">ref_mol</span><span class=\"p\">),</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ga_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">ga_times</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span><span class=\"p\">)</span>\n    <span class=\"n\">max_generations</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">tanimoto_ga_run</span><span class=\"p\">.</span><span class=\"n\">generation</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Genetic algorithm run took </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds on average, with a maximum of </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">max_generations</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> generations\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Calculate improvement\n</span><span class=\"n\">tanimoto_time</span> <span class=\"o\">=</span> <span class=\"n\">tanimoto_end</span> <span class=\"o\">-</span> <span class=\"n\">tanimoto_start</span>\n<span class=\"n\">improvement</span> <span class=\"o\">=</span> <span class=\"n\">tanimoto_time</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"GA was </span><span class=\"si\">{</span><span class=\"n\">improvement</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> times faster than scoring the virtual library with Tanimoto similarity\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Scoring the virtual library\nTanimoto calculations took 7.41 seconds\nRunning genetic algorithm 10 times\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:07&lt;00:00,  1.29it/s]\n\nGenetic algorithm run took 0.77 seconds on average, with a maximum of 9 generations\nGA was 9.60 times faster than scoring the virtual library with Tanimoto similarity\n</code></pre></div></div>","doi":"https://doi.org/10.59350/7tack-xqe19","guid":"https://jonswain.github.io/ultra-large-libraries-part-2","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1735776000,"rid":"t0c15-jak63","summary":"This is part 2 of a a planned three post series on working with large chemical libraries. The notebook used to create this post, and all the files can be found in this github repo. Combinatorial libraries Combinatorial libraries can grow quickly, combining three sets of 1,000 building blocks allows you to access one billion possibilities.","tags":["Ai","Cheminformatics","Data-science","Genetic-algorithms","Ultra-large-libraries"],"title":"Working with Large Virtual Chemical Libraries: Part 2 - Genetic Algorithms","updated_at":1788763530,"url":"https://jonswain.github.io/ultra-large-libraries-part-2/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>Deep Learning models have traditionally performed well on unstructured data such as text and images, but poorly on structured tabular data, and are usually outperformed by Gradient Boosted Decision Trees (GBDTs) on tabular chemical data. TabPFN (Tabular Prior-data Fitted Network) is a transformer-based foundation model for tabular data, pre-trained on millions of synthetic datasets to solve supervised learning tasks, with state-of-the-art performance on benchmarks. But does it work for cheminformatics?</p>\n<hr/>\n<h2 id=\"tabpfn\">TabPFN</h2>\n<p>A recent paper published in Nature, <a href=\"https://doi.org/10.1038/s41586-024-08328-6\">Accurate predictions on small data with a tabular foundation model by Hollmann et al.</a>, describes a new Deep Learning (DL) algorithm for making predictions on tabular data, TabPFN (Tabular Prior-data Fitted Network). TabPFN is a transformer-based foundation model for tabular data that operates via in-context learning (ICL), enabling it to train and predict on an entire dataset in a single forward pass.</p>\n<p>The authors generated over 100 million synthetic datasets using structural causal models (SCMs). These models simulate causal relationships and mimic real-world tabular data challenges, such as non-linear relationships, missing values, outliers, and diverse feature types (e.g., categorical, ordinal, numerical). These datasets sampled high-level hyperparameters (such as dataset size and number of features), both classification and regression tasks, and Gaussian noise was added to mimic real-world complexities.</p>\n<p>These synthetic datasets were used to pre-train a transformer model. During the training, parts of the datasets were masked, and the model is trained to predict masked target values in synthetic datasets, given features and labelled samples as context. The parameters of the neural network were updated until the predictions matched the masked values. Through this training, the model learns to fill in missing data from a dataset. The model learns a generic prediction algorithm that approximates Bayesian inference for the synthetic data prior, enabling robust handling of unseen datasets.</p>\n<p>When making predictions TabPFN uses ICL and processes an entire dataset (both labelled and test samples) in one pass, performing training and inference simultaneously.</p>\n<h2 id=\"therapeutic-data-commons-tdc\">Therapeutic data commons (TDC)</h2>\n<p>The <a href=\"https://tdcommons.ai/\">Therapeutic data commons (TDC)</a> aims to help development of AI/ML tools for therapeutic science by providing datasets and curated benchmarks to assess the performance of new methods. The TDC ADMET benchmark group contains 22 datasets for molecular property prediction, ranging from 475 to 13,130 entries, with both classification and regression tasks. The datasets contain SMILES strings for each chemical compound, and a target variable to be predicted. These can all be easily downloaded using their Python library.</p>\n<h2 id=\"using-tabpfn-on-tdc-datasets\">Using TabPFN on TDC datasets</h2>\n<p>TabPFN is designed for small tabular datasets, and the default parameters limit to training on 10,000 entries with 500 features. If a dataset had more than 10,000 entries in the training and validation sets, a random selection of 10,000 entries was used. For each dataset, the training and validation data were combined, and the test set put aside. For all entries, the 210 RDKit descriptors were calculated and used as the features for training. Predictions were made on the test set and compared to the true values to evaluate the performance of TabPFN. The training was repeated using molecular fingerprints (MACCS keys and ECPF folded to 500 bits), but the performance was found to be lower than using RDKit calculated properties.</p>\n<p><strong>NOTE</strong>: Due to memory limits on my computer, I had to limit to datasets with fewer than 1,800 entries in the training data. Training models for the larger datasets is ongoing.</p>\n<h2 id=\"performance-on-tdc-datasets\">Performance on TDC datasets</h2>\n<p>Using the RDKit calculated descriptors as features, TabPFN come in the top 10 models for all TDC datasets apart from \"Clearance_Hepatocyte_Az\". It comes 3rd for the \"Vdss_Lombardo\" dataset, 2nd for \"Caco2_Wang\", \"Pgp_Broccatelli\", and \"Bbb_Martins\" datasets, and is the highest performing model for the \"Clearance_Microsome_Az\" dataset. The high performing datasets include both classification and regression tasks and doesn't seem to be a clear link between classification performance and imbalanced datasets. \"Cyp2C9_Substrate_Carbonmangels\" is somewhat imbalanced (19.3% positive - TDC rank: 10th) but so are \"HIA_Hou\" (11.1% negative - TDC rank: 5th), and \"Bioavailability_Ma\", (22.9% negative - TDC rank: 5th). The strongest link seems to be between dataset size and performance, with all the highest performing models having &gt;900 entries.</p>\n<p>The training time is an average of five repeats, using WSL on a Windows machine with 8 GB RAM allocated to WSL.</p>\n<table>\n<thead>\n<tr>\n<th>Dataset</th>\n<th>Size</th>\n<th>Task</th>\n<th>Metric</th>\n<th>Training time (min)</th>\n<th>TabFPN performance</th>\n<th>Current TDC best performance</th>\n<th>TabPFN TDC leaderboard rank</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Caco2_Wang</td>\n<td>906</td>\n<td>Regression</td>\n<td>MAE</td>\n<td>8.25</td>\n<td>0.282 \u00b1 0.005</td>\n<td>0.276 \u00b1 0.005</td>\n<td>2nd</td>\n</tr>\n<tr>\n<td>HIA_Hou</td>\n<td>578</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.17</td>\n<td>0.987 \u00b1 0.001</td>\n<td>0.990 \u00b1 0.002</td>\n<td>5th</td>\n</tr>\n<tr>\n<td>Pgp_Broccatelli</td>\n<td>1218</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>12.77</td>\n<td>0.936 \u00b1 0.004</td>\n<td>0.938 \u00b1 0.002</td>\n<td>2th</td>\n</tr>\n<tr>\n<td>Bioavailability_Ma</td>\n<td>640</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>4.32</td>\n<td>0.735 \u00b1 0.016</td>\n<td>0.753 \u00b1 0.000</td>\n<td>5th</td>\n</tr>\n<tr>\n<td>Bbb_Martins</td>\n<td>2030</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>35.51</td>\n<td>0.917 \u00b1 0.003</td>\n<td>0.920 \u00b1 0.006</td>\n<td>2nd</td>\n</tr>\n<tr>\n<td>Vdss_Lombardo</td>\n<td>1130</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>12.91</td>\n<td>0.693 \u00b1 0.004</td>\n<td>0.713 \u00b1 0.007</td>\n<td>3rd</td>\n</tr>\n<tr>\n<td>Cyp2D6_Substrate_Carbonmangels</td>\n<td>667</td>\n<td>Classification</td>\n<td>AUPRC</td>\n<td>4.82</td>\n<td>0.714 \u00b1 0.009</td>\n<td>0.736</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Cyp3A4_Substrate_Carbonmangels</td>\n<td>670</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.97</td>\n<td>0.641 \u00b1 0.004</td>\n<td>0.667 \u00b1 0.019</td>\n<td>7th</td>\n</tr>\n<tr>\n<td>Cyp2C9_Substrate_Carbonmangels</td>\n<td>669</td>\n<td>Classification</td>\n<td>AUPRC</td>\n<td>4.28</td>\n<td>0.400 \u00b1 0.013</td>\n<td>0.441 \u00b1 0.033</td>\n<td>10th</td>\n</tr>\n<tr>\n<td>Half_Life_Obach</td>\n<td>667</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>4.20</td>\n<td>0.546 \u00b1 0.013</td>\n<td>0.576 \u00b1 0.025</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Clearance_Microsome_Az</td>\n<td>1102</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>12.71</td>\n<td>0.632 \u00b1 0.006</td>\n<td>0.630 \u00b1 0.010</td>\n<td>1st</td>\n</tr>\n<tr>\n<td>Clearance_Hepatocyte_Az</td>\n<td>1213</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>11.27</td>\n<td>0.391 \u00b1 0.004</td>\n<td>0.536 \u00b1 0.02</td>\n<td>&gt;10th</td>\n</tr>\n<tr>\n<td>Herg</td>\n<td>655</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.54</td>\n<td>0.850 \u00b1 0.002</td>\n<td>0.880 \u00b1 0.002</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Dili</td>\n<td>475</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>1.92</td>\n<td>0.910 \u00b1 0.005</td>\n<td>0.925 \u00b1 0.005</td>\n<td>6th</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"advantages\">Advantages</h2>\n<p>On some of the TDC datasets, TabPFN exhibits state-of-the-art performance out of the box. Regularly outperforming other DL methods such as graph neural networks (GNNs) designed specifically for cheminformatics. It can learn complex relationships between features and the target on small datasets due to the pre-training on synthetic data. TabPFN uses a scikit-learn style API, making it very easy to use and integrate into existing workflows. Since TabPFN approximates Bayesian inference, it can include important features such as uncertainty estimation possible.</p>\n<h2 id=\"limitations\">Limitations</h2>\n<p>TabPFN is limited to 10,000 entries with 500 features as a default, as this is the limit of the synthetic data it was trained on. It is possible to use datasets larger than this using the <code class=\"language-plaintext highlighter-rouge\">ignore_pretraining_limits=True</code> parameter, but this may lead to very long training times. The computational requirements for TabPFN scale quadratically with the number of samples (n) and the number of features (m), i.e. O(n<sup>2</sup> + m<sup>2</sup>) so training and predicting on larger datasets gets increasing longer. The training time can be long, though not significantly worse than other DL methods such as Chemprop in my experience. The single pass training and prediction architecture is not great for situations when you need to make multiple predictions, but the authors have included an option to cache the effect of the training data (using the <code class=\"language-plaintext highlighter-rouge\">fit_mode=\"fit_with_cache\"</code> parameter) when training the model, making the training and prediction API much more like a standard scikit-learn ML model.</p>\n<h2 id=\"conclusions\">Conclusions</h2>\n<p>TabPFN is definitely a method to consider when building QSAR models, especially when you have a small dataset. With more time (and computing power), I'd be interested to see if combining the RDKit calculated descriptors with a molecular fingerprint (i.e. adding MACCS keys would make 376 feature columns) improves performance. In my experience sometimes combining the local description of molecules using molecular fingerprints with global descriptions of molecules using calculated descriptors creates a very good embedding of your molecules.</p>\n<p>It's possible to fine tune TabPFN with specialised datasets. Perhaps fine tuning with a large number of chemical datasets, or synthetic datasets of calculated molecular properties would create a chemical tabular foundation model (ChemTabPFN?), capable of accurately predicting chemical property-structure relationships.</p>\n<h2 id=\"references\">References</h2>\n<ul>\n<li>The code used can be found in <a href=\"https://github.com/jonswain/tabpfn-tdc\">this Github repo</a></li>\n<li><a href=\"https://github.com/PriorLabs/tabpfn\">TabPFN Github repo</a></li>\n</ul>","doi":"https://doi.org/10.59350/y9a60-68b25","guid":"https://jonswain.github.io/TabPFN-for-chemical-datasets","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1737504000,"reference":[{"id":"https://github.com/jonswain/tabpfn-tdc","unstructured":"The code used can be found in this Github repo"},{"id":"https://github.com/PriorLabs/tabpfn","unstructured":"TabPFN Github repo"}],"rid":"dcv48-dvh59","summary":"Deep Learning models have traditionally performed well on unstructured data such as text and images, but poorly on structured tabular data, and are usually outperformed by Gradient Boosted Decision Trees (GBDTs) on tabular chemical data.","tags":["Ai","Cheminformatics","Data-science","Machine-learning"],"title":"TabPFN for Chemical Datasets","updated_at":1788763526,"url":"https://jonswain.github.io/tabpfn-for-chemical-datasets/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p><img alt=\"An example notification\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/traffic_widget/traffic_notification.png\"/></p>\n<hr/>\n<p>No data science or cheminformatics today!</p>\n<p>I usually cycle or get public transport to work, but occasionally I do have to drive. The traffic in T\u0101maki Makaurau (Auckland) is very variable, especially on rainy or windy days, so I often find myself checking Google Maps every 5 minutes after 4 pm to work out when I need to leave to get home. As with anything that I have to do repeatedly, <a href=\"https://xkcd.com/1205/\">I decided  to automate it</a>.</p>\n<p>My work computer runs Windows so I use <a href=\"https://learn.microsoft.com/en-us/windows/wsl/about\">WSL</a>, which adds a few extra complications. All the code needed can be found in <a href=\"https://github.com/jonswain/traffic-widget\">this repository</a>.</p>\n<p>(I've also been experimenting with Google Gemini as a coding assistant. This wasn't completely <a href=\"https://en.wikipedia.org/wiki/Vibe_coding\">vibe-coding</a>, but as this was a quick personal project, I was much less vigorous in checking the code it generated compared to more important work!)</p>\n<p>All the Python code is kept in a file called <code class=\"language-plaintext highlighter-rouge\">traffic-widget.py</code> which is stored on my WSL disk, I first needed to import the necessary libraries:</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">datetime</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n<span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">from</span> <span class=\"nn\">dotenv</span> <span class=\"kn\">import</span> <span class=\"n\">load_dotenv</span>\n</code></pre></div></div>\n<h2 id=\"getting-the-traffic-data\">Getting the traffic data</h2>\n<p>Google maps doesn't seem to have a <a href=\"https://mapsplatform.google.com/pricing/\">completely free API</a>. They do offer some free usage, but you still have to sign up and give credit card details, which is always a worry in case you accidentally go over the free limits (or accidentally leak your API key to the internet). TomTom on the other hand does have a <a href=\"https://www.tomtom.com/products/map-display-api/\">free API</a>, you have to sign up and get an API key, and it comes with plenty of free requests. After signing up and getting an API key, I first created a TomTomAPI Python class, this uses the API key to make calls to the route calculation endpoint, providing the GPS coordinates for the start and end of your route, and returns the travel time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">TomTomAPI</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Encapsulates interactions with the TomTom Routing API.\"\"\"</span>\n\n    <span class=\"n\">BASE_URL</span> <span class=\"o\">=</span> <span class=\"s\">\"https://api.tomtom.com/routing/1/calculateRoute\"</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">api_key</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Initialize the TomTomAPI with the API key.\"\"\"</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">api_key</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">get_travel_time</span><span class=\"p\">(</span>\n        <span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">start_lat</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">start_lon</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">end_lat</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">end_lon</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n    <span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">int</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"s\">\"\"\"Calculate the travel time between two points.\n\n        Args:\n            start_lat (float): Latitude of the starting point.\n            start_lon (float): Longitude of the starting point.\n            end_lat (float): Latitude of the destination point.\n            end_lon (float): Longitude of the destination point.\n\n        Returns:\n            int: The travel time in seconds, or None if an error occurs.\n        \"\"\"</span>\n        <span class=\"n\">start_point</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">start_lat</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">start_lon</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n        <span class=\"n\">end_point</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">end_lat</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">end_lon</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n        <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">BASE_URL</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">start_point</span><span class=\"si\">}</span><span class=\"s\">:</span><span class=\"si\">{</span><span class=\"n\">end_point</span><span class=\"si\">}</span><span class=\"s\">/json?key=</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">api_key</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n            <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">raise_for_status</span><span class=\"p\">()</span>\n            <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n            <span class=\"n\">travel_time_seconds</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"routes\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"summary\"</span><span class=\"p\">][</span><span class=\"s\">\"travelTimeInSeconds\"</span><span class=\"p\">]</span>\n            <span class=\"k\">return</span> <span class=\"n\">travel_time_seconds</span>\n\n        <span class=\"k\">except</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">exceptions</span><span class=\"p\">.</span><span class=\"n\">RequestException</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"TomTom API Error: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"bp\">None</span>\n        <span class=\"k\">except</span> <span class=\"p\">(</span><span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">JSONDecodeError</span><span class=\"p\">,</span> <span class=\"nb\">KeyError</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"TomTom API Error: Invalid response format or missing data: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"bp\">None</span>\n</code></pre></div></div>\n<h3 id=\"create-the-powershell-script-for-the-notification\">Create the PowerShell script for the notification</h3>\n<p>To display the notifications I used BurntToast, a Windows PowerShell module for displaying Toast Notifications. To install, I needed to run PowerShell as admin and enter:</p>\n<pre><code class=\"language-PowerShell\">Install-Module -Name BurntToast\n</code></pre>\n<p>BurntToast can be called from a PowerShell file (<code class=\"language-plaintext highlighter-rouge\">.ps1</code>). The PowerShell file to display notifications is called <code class=\"language-plaintext highlighter-rouge\">show_notification.ps1</code> and is stored on my Windows disk. The <code class=\"language-plaintext highlighter-rouge\">-Sound Alarm5</code> adds a sound to the notification and makes it last longer and <code class=\"language-plaintext highlighter-rouge\">-AppLogo</code> gives an icon beside the notification.</p>\n<pre><code class=\"language-ps1\">param(\n    [string]$Title,\n    [string]$Message\n)\n\n$ImagePath = 'C:\\Path\\to\\icon.jpg'\n\nNew-BurntToastNotification -Text $Title, $Message -AppLogo $ImagePath -Sound Alarm5\n</code></pre>\n<p>To allow the script to run, it may be necessary to run this command in PowerShell as admin:</p>\n<pre><code class=\"language-PowerShell\">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n</code></pre>\n<h2 id=\"running-the-powershell-script-from-wsl\">Running the PowerShell script from WSL</h2>\n<p>Back in the Python file (<code class=\"language-plaintext highlighter-rouge\">traffic-widget.py</code>), I next needed a class to run the PowerShell file to display the notification.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">WindowsNotifier</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Handles displaying Windows toast notifications.\"\"\"</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">powershell_path</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">powershell_script</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Initialize the WindowsNotifier with the paths to PowerShell.\"\"\"</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_path</span> <span class=\"o\">=</span> <span class=\"n\">powershell_path</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_script</span> <span class=\"o\">=</span> <span class=\"n\">powershell_script</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">show_notification</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">title</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Show a Windows toast notification.\n\n        Args:\n            title (str): The title of the notification.\n            message (str): The body of the notification.\n        \"\"\"</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n                <span class=\"p\">[</span>\n                    <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_path</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-ExecutionPolicy\"</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"Bypass\"</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-File\"</span><span class=\"p\">,</span>\n                    <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_script</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-Title\"</span><span class=\"p\">,</span>\n                    <span class=\"n\">title</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-Message\"</span><span class=\"p\">,</span>\n                    <span class=\"n\">message</span><span class=\"p\">,</span>\n                <span class=\"p\">],</span>\n                <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n                <span class=\"n\">capture_output</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n                <span class=\"n\">text</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n        <span class=\"k\">except</span> <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">CalledProcessError</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error showing notification: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"PowerShell Output:</span><span class=\"se\">\\n</span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"p\">.</span><span class=\"n\">stderr</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n        <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error: PowerShell executable or script not found. Check the paths.\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>This function formats the data from the TomTomAPI to be clearer.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">format_travel_time</span><span class=\"p\">(</span><span class=\"n\">label</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">travel_time</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Format the travel time into a readable string.\n\n    Args:\n        label (str): The label for the travel time (e.g., \"Home\").\n        travel_time (int | None): The travel time in seconds, or None if an error occurred.\n\n    Returns:\n        str: The formatted travel time string.\n    \"\"\"</span>\n    <span class=\"n\">now</span> <span class=\"o\">=</span> <span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n    <span class=\"n\">arrival_time</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">now</span> <span class=\"o\">+</span> <span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">seconds</span><span class=\"o\">=</span><span class=\"n\">travel_time</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">travel_time</span> <span class=\"k\">else</span> <span class=\"bp\">None</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">travel_time</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">label</span><span class=\"si\">}</span><span class=\"s\">: </span><span class=\"si\">{</span><span class=\"n\">travel_time</span> <span class=\"o\">/</span> <span class=\"mi\">60</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">1</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> minutes (arrive at </span><span class=\"si\">{</span><span class=\"n\">arrival_time</span><span class=\"p\">.</span><span class=\"n\">strftime</span><span class=\"p\">(</span><span class=\"s\">'%H</span><span class=\"si\">:</span><span class=\"o\">%</span><span class=\"n\">M</span><span class=\"s\">')</span><span class=\"si\">}</span><span class=\"s\">)\"</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">label</span><span class=\"si\">}</span><span class=\"s\">: ERROR minutes.\"</span>\n</code></pre></div></div>\n<h2 id=\"storing-environment-variables\">Storing environment variables</h2>\n<p>To prevent sharing sensitive data such as my API key and home address, I stored these in a <code class=\"language-plaintext highlighter-rouge\">.env</code> file. This also stores the paths to Windows PowerShell and the PowerShell script. Since this is working between two operating systems, the paths are slightly more complicated than usual. The PowerShell path is the path to the <code class=\"language-plaintext highlighter-rouge\">powershell.exe</code> file on your Windows disk, from your Linux environment. This is usually something like: <code class=\"language-plaintext highlighter-rouge\">/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe</code>. The PowerShell script is in Windows format with escaped backslashes.</p>\n<pre><code class=\"language-env\">API_KEY = \"your_api_key\"\nWORK_LATITUDE = \"work_latitude\"\nWORK_LONGITUDE = \"work_longitude\"\nHOME_LATITUDE = \"home_latitude\"\nHOME_LONGITUDE = \"home_longitude\"\n\nPOWERSHELL_PATH = \"/mnt/c/path/to/powershell.exe\"\nPOWERSHELL_SCRIPT = \"C:\\\\windows\\\\path\\\\to\\\\show_notification.ps1\"\n</code></pre>\n<h2 id=\"creating-a-conda-environment-to-run-the-script\">Creating a conda environment to run the script</h2>\n<p>To run the Python script I used a conda environment. It can be created and activated with:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda <span class=\"nb\">env </span>create <span class=\"nt\">-f</span> environment.yml\nconda activate traffic-widget\n</code></pre></div></div>\n<h2 id=\"running-the-script\">Running the script</h2>\n<p>Finally I needed some Python code to run the whole process:</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">\"__main__\"</span><span class=\"p\">:</span>\n    <span class=\"n\">load_dotenv</span><span class=\"p\">()</span>\n    <span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"API_KEY\"</span><span class=\"p\">]</span>\n    <span class=\"n\">work_lat</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"WORK_LATITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">work_lon</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"WORK_LONGITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">home_lat</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"HOME_LATITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">home_lon</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"HOME_LONGITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">powershell_path</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s\">\"POWERSHELL_PATH\"</span><span class=\"p\">)</span>\n    <span class=\"n\">powershell_script</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s\">\"POWERSHELL_SCRIPT\"</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">powershell_script</span><span class=\"p\">:</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Error: POWERSHELL_SCRIPT environment variable not set.\"</span><span class=\"p\">)</span>\n        <span class=\"nb\">exit</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n    <span class=\"n\">tomtom_api</span> <span class=\"o\">=</span> <span class=\"n\">TomTomAPI</span><span class=\"p\">(</span><span class=\"n\">api_key</span><span class=\"p\">)</span>\n    <span class=\"n\">notifier</span> <span class=\"o\">=</span> <span class=\"n\">WindowsNotifier</span><span class=\"p\">(</span><span class=\"n\">powershell_path</span><span class=\"p\">,</span> <span class=\"n\">powershell_script</span><span class=\"p\">)</span>\n\n    <span class=\"n\">home_travel_time</span> <span class=\"o\">=</span> <span class=\"n\">tomtom_api</span><span class=\"p\">.</span><span class=\"n\">get_travel_time</span><span class=\"p\">(</span>\n        <span class=\"n\">work_lat</span><span class=\"p\">,</span> <span class=\"n\">work_lon</span><span class=\"p\">,</span> <span class=\"n\">home_lat</span><span class=\"p\">,</span> <span class=\"n\">home_lon</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"n\">message_lines</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"n\">format_travel_time</span><span class=\"p\">(</span><span class=\"s\">\"Home\"</span><span class=\"p\">,</span> <span class=\"n\">home_travel_time</span><span class=\"p\">),</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">notification_message</span> <span class=\"o\">=</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">message_lines</span><span class=\"p\">)</span>\n\n    <span class=\"n\">notifier</span><span class=\"p\">.</span><span class=\"n\">show_notification</span><span class=\"p\">(</span><span class=\"s\">\"Driving times:\"</span><span class=\"p\">,</span> <span class=\"n\">notification_message</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>Manually running the script with <code class=\"language-plaintext highlighter-rouge\">python traffic-widget.py</code> should cause the following pop-up:</p>\n<p><img alt=\"An example notification\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/traffic_widget/traffic_notification.png\"/></p>\n<h2 id=\"setting-up-a-cron-job-to-automatically-run-the-script\">Setting up a cron job to automatically run the script</h2>\n<p>To make the script run automatically, back in WSL, I ran:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>crontab <span class=\"nt\">-e</span>\n</code></pre></div></div>\n<p>And added the details for the cron job. I wanted mine to run every 5 minutes from 4-5 pm on weekdays.</p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>0-55/5 16 * * 1-5 /home/<username>/miniconda3/envs/traffic-widget/bin/python /home/<username>/path/to/traffic-widget/traffic-widget.py\n</username></username></code></pre></div></div>","doi":"https://doi.org/10.59350/8kc3w-r1780","guid":"https://jonswain.github.io/building-a-traffic-widget","image":"https://jonswain.github.io/images/traffic_widget/traffic_notification.png","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1746057600,"rid":"zh250-47j23","summary":"No data science or cheminformatics today! I usually cycle or get public transport to work, but occasionally I do have to drive. The traffic in T\u0101maki Makaurau (Auckland) is very variable, especially on rainy or windy days, so I often find myself checking Google Maps every 5 minutes after 4 pm to work out when I need to leave to get home.","tags":["Software-projects"],"title":"Building a Traffic Reminder Widget","updated_at":1788763524,"url":"https://jonswain.github.io/building-a-traffic-reminder-widget/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>An unwelcome cyclone may have cancelled my hiking plans, but it gave me the opportunity to delve into the fascinating world of drug repurposing. This blog post explores how identifying new uses for existing drugs can dramatically accelerate and reduce the cost of bringing vital treatments to patients, and how recent advances in machine learning can further streamline this process.</p>\n<hr/>\n<p>Over the Easter long weekend, T\u0101maki Makaurau (Auckland) was visited by Cyclone Tam, which meant my hiking trip was cancelled. At least it gave me some time to work on a few projects and finish this blog post I'd been meaning to do for a while!</p>\n<h2 id=\"drug-repurposing\">Drug Repurposing</h2>\n<p>Traditional drug discovery is a marathon, often taking over a decade and requiring an investment of more than a billion dollars for each successful drug. This lengthy and costly process highlights the urgent need for more efficient strategies to combat disease. One such promising strategy is drug repurposing: identifying new therapeutic uses for existing drugs or those already undergoing clinical trials. By bypassing much of the early-stage development, and sometimes significant parts of clinical trials, drug repurposing offers the potential to significantly reduce both the time and financial cost associated with bringing new treatments to patients. Drug repurposing is ideal for addressing therapeutic needs in disease areas where financial incentives are limited (such as neglected tropical diseases and antibiotic development), or for rapid deployment in situations demanding swift treatment interventions for imminent public health emergencies, such as during the COVID-19 pandemic.</p>\n<p>One well-known example of drug repurposing is sildenafil, originally developed to treat high blood pressure and angina (chest pain), and famously repurposed for erectile dysfunction after clinical trials revealed this unexpected side effect. Another is thalidomide, developed to treat morning sickness and found to cause birth defects, which is now used to treat cancers and skin disorders associated with leprosy. The crucial role of drug repurposing was further highlighted during the recent COVID-19 pandemic, where the corticosteroid dexamethasone emerged as a vital intervention in reducing mortality among severely ill patients.</p>\n<p>Machine learning offers a novel method for drug repurposing. Using high-quality activity data for a target of interest, a machine learning model can be trained to predict active compounds based on their molecular structure. This trained model can then be used to screen libraries of compounds for potential repurposing.</p>\n<h2 id=\"the-drug-repurposing-hub\">The Drug Repurposing Hub</h2>\n<p>The <a href=\"https://www.nature.com/articles/nm.4306.epdf\">Drug Repurposing Hub</a> is a curated and annotated collection of drugs that have already received FDA approval or are currently undergoing clinical trials. Maintained by the Broad Institute, this resource serves as both a virtual library, providing comprehensive information on each compound, and a physical library, offering access to compound plates for experimental screening.</p>\n<h2 id=\"antibiotic-discovery-using-deep-learning\">Antibiotic Discovery using Deep Learning</h2>\n<p>This post was inspired by the work of the <a href=\"https://www.cell.com/cell/fulltext/S0092-8674(20)30102-1\">Collins Lab at MIT</a>, who used deep learning techniques to identify potential compounds for repurposing as treatments for antimicrobial-resistant bacteria. Their research addresses the escalating crisis of antibiotic resistance, a looming threat projected to cause 10 million deaths annually by 2050.</p>\n<p>To tackle this challenge, the researchers assembled a primary training set of 2,335 molecules, experimentally screening them against <em>E. coli</em> BW25113 and identifying 120 compounds with antimicrobial activity. With the goal of creating a robust and generalizable model, they ensured the training set was structurally diverse. This data was then used to train a directed message-passing neural network (dMPNN) to create a classification model capable of predicting antimicrobial activity based on a molecule's chemical structure. The trained model was subsequently used to make predictions on virtual libraries of compounds, including the Drug Repurposing Hub.</p>\n<p>Ninety-nine molecules from the Drug Repurposing Hub that were predicted to be active were experimentally tested, with 51 displaying growth inhibition against <em>E. coli</em>. This process led to the identification of a particularly promising compound, the c-Jun N-terminal kinase inhibitor SU3327, which the researchers named halicin. Notably, halicin possesses a structure distinctly different from conventional antibiotics and demonstrated potent inhibitory activity against <em>E. coli</em> growth.</p>\n<h2 id=\"chemprop\">Chemprop</h2>\n<p>Central to this research is the open-source software package <a href=\"https://chemprop.readthedocs.io/en/latest/\">Chemprop</a>. Developed primarily by researchers at MIT using the PyTorch framework, Chemprop harnesses the capabilities of message-passing neural networks (MPNNs) for molecular property prediction. Chemprop can be used both as a command-line interface (CLI) and a Python API. It implements a d-MPNN architecture, a type of graph neural network particularly effective for extracting relationships from molecular structures represented as graphs. An advantage of Chemprop is its ability to learn directly from molecular representations, such as SMILES strings converted into molecular graphs, eliminating the need for manual feature engineering, which can also introduce biases.</p>\n<h2 id=\"chembl\">ChEMBL</h2>\n<p>Lacking access to a physical lab for experimental data, I needed an alternative approach for data gathering. For this, I used ChEMBL, a freely accessible and meticulously curated chemical database housing bioactivity data for drug-like molecules. It is maintained by the European Bioinformatics Institute (EBI), part of the European Molecular Biology Laboratory (EMBL) in the UK. Using the <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC4489243/\">ChEMBL Webservice</a>, I was able to specifically download data relevant to the target of interest. This retrieved data then served as the foundational dataset for training a machine learning model, which was subsequently employed to virtually screen the compounds within The Drug Repurposing Hub.</p>\n<h2 id=\"imports\">Imports</h2>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">chembl_webresource_client.new_client</span> <span class=\"kn\">import</span> <span class=\"n\">new_client</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">Draw</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"models\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"getting-the-data-from-chembl\">Getting the Data from ChEMBL</h2>\n<p>Using the ChEMBL Webservice, I downloaded the data associated with a target for training a machine learning model using the three functions below. The first searches ChEMBL with a target name, and returns the target ChEMBL ID of the most similar target. The second downloads the activity data associated that the target ChEMBL ID, along with the molecular ChEMBL IDs. The third returns the canonical SMILES for the molecular ChEMBL IDs.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">target</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">target</span>\n<span class=\"n\">activity</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">activity</span>\n<span class=\"n\">molecule</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">molecule</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_target_chembl_id</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the ChEMBL ID of a target given its name.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Searching for target: \"</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"'</span><span class=\"p\">)</span>\n    <span class=\"n\">target_query</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">search</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">)</span>\n    <span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"n\">chembl_id</span> <span class=\"o\">=</span> <span class=\"n\">target_query</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"pref_name\"</span><span class=\"p\">],</span> <span class=\"n\">target_query</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"target_chembl_id\"</span><span class=\"p\">]</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Target name: </span><span class=\"si\">{</span><span class=\"n\">name</span><span class=\"si\">}</span><span class=\"s\">, ChEMBL ID: </span><span class=\"si\">{</span><span class=\"n\">chembl_id</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">chembl_id</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_target_activity</span><span class=\"p\">(</span><span class=\"n\">chembl_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the activity data for a target given its ChEMBL ID.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Getting activity data for </span><span class=\"si\">{</span><span class=\"n\">chembl_id</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">activities</span> <span class=\"o\">=</span> <span class=\"n\">activity</span><span class=\"p\">.</span><span class=\"nb\">filter</span><span class=\"p\">(</span>\n        <span class=\"n\">target_chembl_id</span><span class=\"o\">=</span><span class=\"n\">chembl_id</span><span class=\"p\">,</span>\n        <span class=\"n\">standard_type</span><span class=\"o\">=</span><span class=\"s\">\"IC50\"</span><span class=\"p\">,</span>\n        <span class=\"n\">relation</span><span class=\"o\">=</span><span class=\"s\">\"=\"</span><span class=\"p\">,</span>\n        <span class=\"n\">standard_units</span><span class=\"o\">=</span><span class=\"s\">\"nM\"</span><span class=\"p\">,</span>\n    <span class=\"p\">).</span><span class=\"n\">only</span><span class=\"p\">(</span>\n        <span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"standard_value\"</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Found </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">activities</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> activities\"</span><span class=\"p\">)</span>\n    <span class=\"n\">activities_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">activities</span><span class=\"p\">[:</span><span class=\"mi\">100</span><span class=\"p\">],</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Processing Activities\"</span><span class=\"p\">))</span>\n    <span class=\"p\">)[[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"standard_value\"</span><span class=\"p\">]]</span>\n    <span class=\"n\">activities_df</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">to_numeric</span><span class=\"p\">(</span><span class=\"n\">activities_df</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">])</span>\n    <span class=\"k\">return</span> <span class=\"n\">activities_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_molecule_data</span><span class=\"p\">(</span><span class=\"n\">molecule_chembl_ids</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the molecule data for a list of ChEMBL IDs.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Getting molecule data for </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">molecule_chembl_ids</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> ChEMBL IDs\"</span><span class=\"p\">)</span>\n    <span class=\"n\">compounds_provider</span> <span class=\"o\">=</span> <span class=\"n\">molecule</span><span class=\"p\">.</span><span class=\"nb\">filter</span><span class=\"p\">(</span>\n        <span class=\"n\">molecule_chembl_id__in</span><span class=\"o\">=</span><span class=\"n\">molecule_chembl_ids</span>\n    <span class=\"p\">).</span><span class=\"n\">only</span><span class=\"p\">(</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"molecule_structures\"</span><span class=\"p\">)</span>\n    <span class=\"n\">compounds_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">json_normalize</span><span class=\"p\">(</span>\n        <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">compounds_provider</span><span class=\"p\">,</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Processing Compounds\"</span><span class=\"p\">))</span>\n    <span class=\"p\">)[[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"molecule_structures.canonical_smiles\"</span><span class=\"p\">]].</span><span class=\"n\">rename</span><span class=\"p\">(</span>\n        <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s\">\"molecule_structures.canonical_smiles\"</span><span class=\"p\">:</span> <span class=\"s\">\"canonical_smiles\"</span><span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">compounds_df</span>\n</code></pre></div></div>\n<h2 id=\"format-the-data-for-chemprop\">Format the Data for Chemprop</h2>\n<p>Chemprop takes the data in the form of a table containing one column of SMILES strings, and one columns of activities. For this example I am creating a classification model, so the activity column is a binary classification (1 or 0). The first function takes the activity data and molecule data and merges them on the molecule ChEMBL ID, before cleaning the data up and creating the binary variable based on a cutoff. This cutoff can be changed depending on the target and dataset. I've always found the CLI usage of Chemprop easier, so the data is saved as a CSV file for training.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">create_training_data</span><span class=\"p\">(</span>\n    <span class=\"n\">activity_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">molecule_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Create the training data for the model.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating training data\"</span><span class=\"p\">)</span>\n    <span class=\"n\">training_data</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"p\">(</span>\n            <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">merge</span><span class=\"p\">(</span>\n                <span class=\"n\">left</span><span class=\"o\">=</span><span class=\"n\">activity_data</span><span class=\"p\">,</span>\n                <span class=\"n\">right</span><span class=\"o\">=</span><span class=\"n\">molecule_data</span><span class=\"p\">,</span>\n                <span class=\"n\">on</span><span class=\"o\">=</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span>\n                <span class=\"n\">how</span><span class=\"o\">=</span><span class=\"s\">\"left\"</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">how</span><span class=\"o\">=</span><span class=\"s\">\"any\"</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">drop</span><span class=\"p\">(</span><span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"canonical_smiles\"</span><span class=\"p\">,</span> <span class=\"n\">as_index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">agg</span><span class=\"p\">(</span><span class=\"s\">\"mean\"</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">]</span> <span class=\"o\">&lt;</span> <span class=\"n\">nm_cutoff</span><span class=\"p\">).</span><span class=\"n\">astype</span><span class=\"p\">(</span>\n        <span class=\"nb\">int</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">actives</span> <span class=\"o\">=</span> <span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">].</span><span class=\"nb\">sum</span><span class=\"p\">()</span>\n    <span class=\"n\">inactives</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">)</span> <span class=\"o\">-</span> <span class=\"n\">actives</span>\n    <span class=\"n\">percentage</span> <span class=\"o\">=</span> <span class=\"mi\">100</span> <span class=\"o\">*</span> <span class=\"n\">actives</span> <span class=\"o\">/</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Actives: </span><span class=\"si\">{</span><span class=\"n\">actives</span><span class=\"si\">}</span><span class=\"s\"> (</span><span class=\"si\">{</span><span class=\"n\">percentage</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">%), Inactives: </span><span class=\"si\">{</span><span class=\"n\">inactives</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">training_data</span><span class=\"p\">.</span><span class=\"n\">drop</span><span class=\"p\">(</span><span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">save_training_data</span><span class=\"p\">(</span>\n    <span class=\"n\">training_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"p\">:</span> <span class=\"n\">Path</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Save the training data to a CSV file.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Saving training data to </span><span class=\"si\">{</span><span class=\"n\">output_dir</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">output_dir</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">parents</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> <span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">training_data</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"n\">output_dir</span> <span class=\"o\">/</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">_training_data.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Training data saved to </span><span class=\"si\">{</span><span class=\"n\">output_dir</span> <span class=\"o\">/</span> <span class=\"sa\">f</span><span class=\"s\">'</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"n\">_training_data</span><span class=\"p\">.</span><span class=\"n\">csv</span><span class=\"s\">'</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"download-the-drug-repurposing-hub-dataset\">Download the Drug Repurposing Hub dataset</h2>\n<p>This function downloaded the Drug Repurposing Hub dataset, tidies the data up and saves it for making predictions using Chemprop.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">canon_smiles</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Convert SMILES string to canonical SMILES.\"\"\"</span>\n    <span class=\"n\">mol</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">mol</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"bp\">None</span>\n    <span class=\"k\">return</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">,</span> <span class=\"n\">canonical</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">download_drug_repurposing_hub</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Download the Drug Repurposing Hub dataset.\"\"\"</span>\n    <span class=\"k\">if</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">).</span><span class=\"n\">exists</span><span class=\"p\">():</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Drug Repurposing Hub dataset already downloaded\"</span><span class=\"p\">)</span>\n        <span class=\"k\">return</span> <span class=\"bp\">None</span>\n    <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">\"https://storage.googleapis.com/cdot-general-storage/repurposing_samples_20240610.txt\"</span>\n    <span class=\"n\">drug_repurposing_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"n\">url</span><span class=\"p\">,</span>\n        <span class=\"n\">sep</span><span class=\"o\">=</span><span class=\"s\">\"</span><span class=\"se\">\\t</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"n\">skiprows</span><span class=\"o\">=</span><span class=\"mi\">9</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">].</span><span class=\"nb\">apply</span><span class=\"p\">(</span><span class=\"n\">canon_smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">],</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">drop_duplicates</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">],</span> <span class=\"n\">keep</span><span class=\"o\">=</span><span class=\"s\">\"first\"</span><span class=\"p\">,</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Drug Repurposing Hub dataset downloaded and processed\"</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Number of unique SMILES: </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">'smiles'</span><span class=\"p\">].</span><span class=\"n\">unique</span><span class=\"p\">())</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"make-predictions-on-the-drug-repurposing-hub-data\">Make Predictions on the Drug Repurposing Hub Data</h2>\n<p>As I mentioned above, I find the CLI usage of Chemprop easier (especially since the move from v1 to v2), so here I'm using subprocess to run Chemprop. These two functions train a Chemprop model, and make predictions on the Drug Repurposing Hub data.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">data_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train a ChemProp model using the training data.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Training ChemProp model for </span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">chemprop_train_args</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"train\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--data-path\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">data_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_training_data.csv\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--task-type\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"classification\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--save-dir\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">output_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--split-type\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"scaffold_balanced\"</span><span class=\"p\">,</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">chemprop_train_args</span><span class=\"p\">,</span> <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">predict_with_chemprop</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">preds_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">model_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Predict using the ChemProp model.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Predicting with ChemProp model for </span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">chemprop_predict_args</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"predict\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--model-paths\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">model_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--smiles-columns\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"smiles\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--preds-path\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">preds_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_predictions.csv\"</span><span class=\"p\">,</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">chemprop_predict_args</span><span class=\"p\">,</span> <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"visualize-the-top-predictions\">Visualize the Top Predictions</h2>\n<p>Once we've scored the compounds, we want to take a look at the top scoring compounds.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Visualize the top predictions.\"\"\"</span>\n    <span class=\"n\">predictions_df</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">search</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_predictions.csv\"</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"activity\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"mi\">9</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">Draw</span><span class=\"p\">.</span><span class=\"n\">MolsToGridImage</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">predictions_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">]],</span>\n        <span class=\"n\">legends</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">a</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span> <span class=\"k\">for</span> <span class=\"n\">a</span> <span class=\"ow\">in</span> <span class=\"n\">predictions_df</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">]],</span>\n        <span class=\"n\">molsPerRow</span><span class=\"o\">=</span><span class=\"mi\">3</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"create-a-drug-repurposing-pipeline\">Create a Drug Repurposing Pipeline</h2>\n<p>This function runs the whole process for a specified target search.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"mi\">25</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run the drug repurposing pipeline.\"\"\"</span>\n    <span class=\"n\">download_drug_repurposing_hub</span><span class=\"p\">()</span>\n    <span class=\"n\">target_chembl_id</span> <span class=\"o\">=</span> <span class=\"n\">get_target_chembl_id</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n    <span class=\"n\">activity_data</span> <span class=\"o\">=</span> <span class=\"n\">get_target_activity</span><span class=\"p\">(</span><span class=\"n\">target_chembl_id</span><span class=\"p\">)</span>\n    <span class=\"n\">molecule_data</span> <span class=\"o\">=</span> <span class=\"n\">get_molecule_data</span><span class=\"p\">(</span><span class=\"n\">activity_data</span><span class=\"p\">[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">].</span><span class=\"n\">tolist</span><span class=\"p\">())</span>\n    <span class=\"n\">training_data</span> <span class=\"o\">=</span> <span class=\"n\">create_training_data</span><span class=\"p\">(</span>\n        <span class=\"n\">activity_data</span><span class=\"p\">,</span> <span class=\"n\">molecule_data</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"n\">nm_cutoff</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">save_training_data</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">,</span> <span class=\"n\">search</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">),</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">))</span>\n    <span class=\"n\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"o\">=</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">data_dir</span><span class=\"o\">=</span><span class=\"s\">\"data\"</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"o\">=</span><span class=\"s\">\"models/\"</span><span class=\"p\">)</span>\n    <span class=\"n\">predict_with_chemprop</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"o\">=</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">preds_dir</span><span class=\"o\">=</span><span class=\"s\">\"data\"</span><span class=\"p\">,</span> <span class=\"n\">model_dir</span><span class=\"o\">=</span><span class=\"s\">\"models/\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"run-the-drug-repurposing-pipeline\">Run the Drug Repurposing Pipeline</h2>\n<h3 id=\"malaria\">Malaria</h3>\n<p>Malaria kills around half a million people per year, with 76% of global malaria deaths in children under 5 years old. It is considered a neglected tropical disease (NTD) due to its significant impact on global health, particularly in low-income countries. It disproportionately affects some of the world's poorest people, and is often overlooked in global health efforts. Drug repurposing could offer alternative therapies significantly cheaper than developing an entirely new drug. Here I look to target the most deadly species of parasite that carries malaria - <em>Plasmodium falciparum</em>. After training the Chemprop model and screening the Drug Repurposing Hub, some compounds are flagged as potential hits and could be investigated experimentally.</p>\n<p>Of the top 10 predictions, <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC10545508/\">pyronaridine</a> and <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC4135840/\">KAF-156</a> are both malaria treatments, and were both in the training data, but interestingly pyronaridine was not classified as a hit in the training data.\n<a href=\"https://pubmed.ncbi.nlm.nih.gov/26366636/\">Carfilzomib</a>, <a href=\"https://www.sciencedirect.com/science/article/abs/pii/S0162013403002538\">TPPS4</a>, <a href=\"https://www.nature.com/articles/s41419-020-03017-4\">nanchangmycin</a>, <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC5554889/\">oprozomib</a>, and <a href=\"https://iris.who.int/handle/10665/64651\">lindane</a> have all been investigated for treating malaria, so the model appears to be picking out the right compounds. <a href=\"https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0288335\">Abemaciclib</a> appears to have been recently investigated for repurposing with malaria. <a href=\"https://www.medchemexpress.com/GS967.html?srsltid=AfmBOop_hg1DsKWTwXXujU9XuCISm0oM2Vchpa5gDLKXUABjR9GkmTqv\">GS-967</a> appears to have no link to malaria yet, but maybe should be investigated experimentally!</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">search</span> <span class=\"o\">=</span> <span class=\"s\">\"Plasmodium falciparum\"</span>\n\n<span class=\"n\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n<span class=\"n\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Drug Repurposing Hub dataset downloaded and processed\nNumber of unique SMILES: 6734\nSearching for target: \"Plasmodium falciparum\"\nTarget name: Plasmodium falciparum, ChEMBL ID: CHEMBL364\nGetting activity data for CHEMBL364\nFound 45314 activities\nProcessing Activities: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 45314/45314 [00:02&lt;00:00, 15928.83it/s]\nGetting molecule data for 45314 ChEMBL IDs\nProcessing Molecules: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 21821/21821 [1:32:46&lt;00:00,  3.92it/s]\nCreating training data\nActives: 2677 (12.30%), Inactives: 19082\nSaving training data to data\nTraining data saved to data/Plasmodium_falciparum_training_data.csv\nTraining ChemProp model for Plasmodium falciparum\nPredicting with ChemProp model for Plasmodium falciparum\n</code></pre></div></div>\n<p><img alt=\"Grid of 2D chemical structures for the top 10 compounds the Chemprop model predicted as Plasmodium falciparum hits\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/drug_repurposing/malaria-hits.png\"/></p>\n<h3 id=\"covid-19\">COVID-19</h3>\n<p>The COVID-19 pandemic has caused the deaths of over 7 million people so far, and in 2020 more than half of the world's population was under some form of lockdown. With the speed that COVID spread around the globe, there was no time to develop new drug from scratch, and drug repurposing was explored in clinics to find treatments. In this example, I didn't find any promising compounds in the Drug Repurposing Hub, but with a much smaller dataset than for malaria, the machine learning model is potentially much less powerfull.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">search</span> <span class=\"o\">=</span> <span class=\"s\">\"SARS-CoV-2\"</span>\n\n<span class=\"n\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"mi\">500</span><span class=\"p\">)</span>\n<span class=\"n\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Drug Repurposing Hub dataset already downloaded\nSearching for target: \"SARS-CoV-2\"\nTarget name: SARS-CoV-2, ChEMBL ID: CHEMBL4303835\nGetting activity data for CHEMBL4303835\nFound 802 activities\nProcessing Activities: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 802/802 [00:00&lt;00:00, 12406.99it/s]\nGetting molecule data for 802 ChEMBL IDs\nProcessing Compounds: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 490/490 [00:00&lt;00:00, 6081.22it/s]\nCreating training data\nActives: 71 (14.55%), Inactives: 417\nSaving training data to data\nTraining data saved to data/SARS-CoV-2_training_data.csv\nTraining ChemProp model for SARS-CoV-2\nPredicting with ChemProp model for SARS-CoV-2\n</code></pre></div></div>\n<p><img alt=\"Grid of 2D chemical structures for the top 10 compounds the Chemprop model predicted as SARS-CoV-2 hits\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/drug_repurposing/covid-hits.png\"/></p>","doi":"https://doi.org/10.59350/qpafc-nt106","guid":"https://jonswain.github.io/drug-repurposing-using-ai","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1746230400,"rid":"yj9hq-h3j13","summary":"An unwelcome cyclone may have cancelled my hiking plans, but it gave me the opportunity to delve into the fascinating world of drug repurposing. This blog post explores how identifying new uses for existing drugs can dramatically accelerate and reduce the cost of bringing vital treatments to patients, and how recent advances in machine learning can further streamline this process.","tags":["Ai","Cheminformatics","Data-science","Drug-repurposing","Machine-learning"],"title":"Drug Repurposing Using Artificial Intelligence","updated_at":1788763519,"url":"https://jonswain.github.io/drug-repurposing-using-artificial-intelligence/","version":"v1"}},{"document":{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>Whilst neural networks (<strong>NNs</strong>) have done amazing things with unstructured data such as text and images, they've traditionally been outperformed on tabular data by Gradient-Boosted Decision Trees (<strong>GBDTs</strong>), although recent advances such as <a href=\"https://github.com/PriorLabs/TabPFN\">TabPFN</a> and <a href=\"https://github.com/soda-inria/tabicl\">TabICL</a> suggest that the performance gap may have closed. <a href=\"https://jonswain.github.io/tabpfn-for-chemical-datasets/\">I've written a bit about TabPFN for chemical datasets here</a>.</p>\n<p>One of the most popular deep learning architectures for chemical property prediction is <a href=\"https://github.com/chemprop/chemprop\">Chemprop</a>, developed by researchers at MIT. A Chemprop model is made up of two NNs. The first is a directed Message Passing Neural Network (<strong>d-MPNN</strong>), a type of Graph Convolutional Neural Network (<strong>GCNN</strong>), that takes a graphical representation of a molecule and converts it to a <strong>molecular embedding</strong>, a vector that describes the original molecule. This molecular embedding is then put through a Feed-Forward Neural Network (<strong>FFN</strong>), a fully connected NN used to make the final prediction. The model is trained end-to-end, with both NNs being updated at the same time to minimise a loss function.</p>\n<p>Once the model is trained, the <strong>d-MPNN</strong> can be used to calculate a molecular embedding for a collection of molecules. This is a learned representation optimised for the specific chemical prediction problem. This learned representation is a vector with a length equal to the number of input nodes in the FFN. This is very similar to a traditional molecular fingerprint, such as a <strong>Morgan fingerprint</strong>.</p>\n<p>A collection of these learned fingerprints forms a 2-dimensional array, which is <strong>tabular data</strong>. Since GBDTs regularly outperform NNs on tabular data, can we improve the performance of Chemprop by replacing the FFN with a GBDT after training the d-MPNN?</p>\n<p>Whilst writing this post, a paper was published in <em>The Journal of Chemical Information and Modeling</em> by Pat Walters et al., <a href=\"https://doi.org/10.1021/acs.jcim.5c01609\">Practically Significant Method Comparison Protocols for Machine Learning in Small Molecule Drug Discovery</a>. This is probably my favourite cheminformatics paper so far this year, with comprehensive guidelines on how to compare machine learning models in a statistically robust way. I've tried to follow these recommendations when comparing models here.</p>\n<p>Below I compare three models: the first is a Random Forest (this is not gradient-boosted, but provides a good baseline without hyperparameter tuning) trained on Morgan fingerprints; the second is a standard Chemprop model; and the third generates learned fingerprints using the Chemprop d-MPNN and then trains a Random Forest model on these. I used two datasets, one for a classification problem and one for a regression problem. The classification data is the <a href=\"https://doi.org/10.1021/acsomega.3c01583\">Bile Salt Export Pump dataset</a> and the regression data is the <a href=\"https://polarishub.io/datasets/biogen/adme-fang-v1\">Polaris LogS dataset</a>, both ideas 'borrowed' from <a href=\"https://practicalcheminformatics.blogspot.com\">Practical Cheminformatics posts</a>.</p>\n<h2 id=\"imports\">Imports</h2>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">collections</span> <span class=\"kn\">import</span> <span class=\"n\">defaultdict</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n<span class=\"kn\">from</span> <span class=\"nn\">typing</span> <span class=\"kn\">import</span> <span class=\"n\">Callable</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">matplotlib.pyplot</span> <span class=\"k\">as</span> <span class=\"n\">plt</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">import</span> <span class=\"nn\">seaborn</span> <span class=\"k\">as</span> <span class=\"n\">sns</span>\n<span class=\"kn\">from</span> <span class=\"nn\">numpy.typing</span> <span class=\"kn\">import</span> <span class=\"n\">NDArray</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">rdFingerprintGenerator</span>\n<span class=\"kn\">from</span> <span class=\"nn\">scipy.stats</span> <span class=\"kn\">import</span> <span class=\"n\">f_oneway</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.base</span> <span class=\"kn\">import</span> <span class=\"n\">BaseEstimator</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.ensemble</span> <span class=\"kn\">import</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">,</span> <span class=\"n\">RandomForestRegressor</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.metrics</span> <span class=\"kn\">import</span> <span class=\"p\">(</span>\n    <span class=\"n\">matthews_corrcoef</span><span class=\"p\">,</span>\n    <span class=\"n\">mean_absolute_error</span><span class=\"p\">,</span>\n    <span class=\"n\">r2_score</span><span class=\"p\">,</span>\n    <span class=\"n\">root_mean_squared_error</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">ShuffleSplit</span>\n<span class=\"kn\">from</span> <span class=\"nn\">statsmodels.stats.multicomp</span> <span class=\"kn\">import</span> <span class=\"n\">pairwise_tukeyhsd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm.notebook</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n\n<span class=\"kn\">from</span> <span class=\"nn\">visualisation</span> <span class=\"kn\">import</span> <span class=\"n\">confusion_matricies</span><span class=\"p\">,</span> <span class=\"n\">scatter_plots</span>\n</code></pre></div></div>\n<h2 id=\"useful-functions\">Useful functions</h2>\n<p>The first function <code class=\"language-plaintext highlighter-rouge\">prepare_data</code> takes the path to a CSV file, creates RDKit molecular objects, removes any that are causing issues, and calculates the Morgan fingerprints for each model.</p>\n<p>The second, <code class=\"language-plaintext highlighter-rouge\">save_data_for_chemprop</code>, formats the data and saves it for training Chemprop models.</p>\n<p>The next three functions: <code class=\"language-plaintext highlighter-rouge\">train_chemprop_model</code>, <code class=\"language-plaintext highlighter-rouge\">make_chemprop_predictions</code>, and <code class=\"language-plaintext highlighter-rouge\">calculate_chemprop_fingerprints</code>, all use subprocess to call the Chemprop CLI. I've generally found it easier to use than the Chemprop Python module.</p>\n<p>The next two: <code class=\"language-plaintext highlighter-rouge\">train_morgan_sklearn_model</code> and <code class=\"language-plaintext highlighter-rouge\">train_chemprop_sklearn_model</code>, use Scikit-learn to train Random Forest models, the first using the previously generated Morgan fingerprints, and the second uses the Chemprop learned fingerprints.</p>\n<p>Finally, <code class=\"language-plaintext highlighter-rouge\">gather_predictions</code> uses all of the trained models to make predictions on a test dataset.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">prepare_data</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">:</span> <span class=\"n\">Path</span><span class=\"p\">,</span> <span class=\"n\">smiles_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Load and clean data, generate Morgan fingerprints.\n\n    Args:\n        data_path (Path): Path to CSV file containing data.\n        smiles_col (str): Name of the column containing SMILES strings.\n        target_col (str): Name of the column containing target variable.\n\n    Returns:\n        tuple[pd.DataFrame, pd.DataFrame]: Cleaned dataframe and dataframe of Morgan\n                                           fingerprints.\n    \"\"\"</span>\n    <span class=\"n\">df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"n\">data_path</span><span class=\"p\">)</span>\n    <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">smiles_col</span><span class=\"p\">].</span><span class=\"nb\">apply</span><span class=\"p\">(</span><span class=\"k\">lambda</span> <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n    <span class=\"n\">df</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">],</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">mfpgen</span> <span class=\"o\">=</span> <span class=\"n\">rdFingerprintGenerator</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">(</span><span class=\"n\">radius</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"n\">fpSize</span><span class=\"o\">=</span><span class=\"mi\">2048</span><span class=\"p\">)</span>\n    <span class=\"n\">fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">mfpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]]</span>\n    <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">fps</span><span class=\"p\">))</span>\n    <span class=\"n\">df</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">[[</span><span class=\"n\">smiles_col</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">]]</span>\n    <span class=\"k\">return</span> <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">fps_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">save_data_for_chemprop</span><span class=\"p\">(</span>\n    <span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">test_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Save training and test data for Chemprop CLI.\n\n    Args:\n        data (pd.DataFrame): Dataframe containing data.\n        project_name (str): Name of the project.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        test_idx (NDArray[np.int64]): List of indices for test data.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n    \"\"\"</span>\n    <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span>\n        <span class=\"n\">parents</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> <span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">].</span><span class=\"n\">to_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">].</span><span class=\"n\">to_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_chemprop_model</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Chemprop model using CLI.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        task_type (str): Type of task ('classification' or 'regression').\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"train\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--data-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--task-type\"</span><span class=\"p\">,</span>\n            <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--output-dir\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">make_chemprop_predictions</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Make predictions using trained Chemprop model.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"predict\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--model-paths\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--preds-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/preds.csv\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">dataset_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Calculate Chemprop fingerprints using trained Chemprop model.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        dataset_type (str): Type of dataset ('train' or 'test').\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"fingerprint\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">dataset_type</span><span class=\"si\">}</span><span class=\"s\">.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--output\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">dataset_type</span><span class=\"si\">}</span><span class=\"s\">_fps.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--model-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--ffn-block-index\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"0\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_morgan_sklearn_model</span><span class=\"p\">(</span>\n    <span class=\"n\">fps_df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">Series</span><span class=\"p\">,</span> <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span> <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Scikit-learn model using Morgan fingerprints.\n\n    Args:\n        fps_df (pd.DataFrame): Dataframe of Morgan fingerprints.\n        y (pd.Series): Series of target variable.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        BaseEstimator: Trained Scikit-learn model.\n    \"\"\"</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"n\">X_train</span> <span class=\"o\">=</span> <span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"n\">y_train</span> <span class=\"o\">=</span> <span class=\"n\">y</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"n\">rf</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_chemprop_sklearn_model</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">Series</span><span class=\"p\">,</span>\n    <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Scikit-learn model using Chemprop fingerprints.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        y (pd.Series): Series of target variable.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        BaseEstimator: Trained Scikit-learn model.\n    \"\"\"</span>\n    <span class=\"n\">X_train</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train_fps_0.csv\"</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"n\">y_train</span> <span class=\"o\">=</span> <span class=\"n\">y</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"n\">rf</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">gather_predictions</span><span class=\"p\">(</span>\n    <span class=\"n\">morgan_rf</span><span class=\"p\">:</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">,</span>\n    <span class=\"n\">chemprop_rf</span><span class=\"p\">:</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">fps_df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">test_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Gather predictions from all models.\n\n    Args:\n        morgan_rf (BaseEstimator): Trained Scikit-learn model on Morgan fingerprints.\n        chemprop_rf (BaseEstimator): Trained Scikit-learn model on Chemprop fingerprints.\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        df (pd.DataFrame): Dataframe containing data.\n        fps_df (pd.DataFrame): Dataframe of Morgan fingerprints.\n        test_idx (NDArray[np.int64]): List of indices for test data.\n        target_col (str): Name of the column containing target variable.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        dict: Updated dictionary with predictions.\n    \"\"\"</span>\n    <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n    <span class=\"n\">rf_preds</span> <span class=\"o\">=</span> <span class=\"n\">morgan_rf</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">])</span>\n    <span class=\"n\">chemprop_rf_preds</span> <span class=\"o\">=</span> <span class=\"n\">chemprop_rf</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span>\n        <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test_fps_0.csv\"</span>\n        <span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">chemprop_probas</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/preds.csv\"</span>\n    <span class=\"p\">)[</span><span class=\"n\">target_col</span><span class=\"p\">]</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">chemprop_preds</span> <span class=\"o\">=</span> <span class=\"n\">chemprop_probas</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">chemprop_preds</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">chemprop_probas</span> <span class=\"o\">&gt;=</span> <span class=\"mf\">0.5</span><span class=\"p\">).</span><span class=\"n\">astype</span><span class=\"p\">(</span><span class=\"nb\">int</span><span class=\"p\">)</span>\n    <span class=\"n\">y_true</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">][</span><span class=\"n\">target_col</span><span class=\"p\">]</span>\n    <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">_fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n        <span class=\"s\">\"rf\"</span><span class=\"p\">:</span> <span class=\"n\">rf_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"chemprop_rf\"</span><span class=\"p\">:</span> <span class=\"n\">chemprop_rf_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">:</span> <span class=\"n\">chemprop_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"true\"</span><span class=\"p\">:</span> <span class=\"n\">y_true</span><span class=\"p\">,</span>\n    <span class=\"p\">}</span>\n    <span class=\"k\">return</span> <span class=\"n\">predictions</span>\n</code></pre></div></div>\n<p>To compare the models, I used 5x5 cross-validation. This approach involves using five different random seeds to generate data splits for 5-fold cross-validation, resulting in 25 unique train-test splits. For each split, the models are trained on the training data and then used to make predictions on the test data.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">:</span> <span class=\"n\">Path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">5</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">5</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run model comparison pipeline.\n\n    Args:\n        data_path (Path): Path to CSV file containing data.\n        project_name (str): Name of the project.\n        smiles_col (str): Name of the column containing SMILES strings.\n        target_col (str): Name of the column containing target variable.\n        task_type (str): Type of task ('classification' or 'regression').\n        n_seeds (int, optional): Number of random seeds for cross-validation. Defaults to 5.\n        n_folds (int, optional): Number of folds for cross-validation. Defaults to 5.\n        test_frac (float, optional): Fraction of data to use as test set. Defaults to 0.2.\n\n    Returns:\n        dict: Dictionary containing predictions from all models.\n    \"\"\"</span>\n    <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">prepare_data</span><span class=\"p\">(</span><span class=\"n\">data_path</span><span class=\"p\">,</span> <span class=\"n\">smiles_col</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">)</span>\n\n    <span class=\"n\">all_predictions</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n    <span class=\"k\">for</span> <span class=\"n\">seed</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">n_seeds</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Seeds\"</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">\"seed\"</span><span class=\"p\">):</span>\n        <span class=\"n\">cv</span> <span class=\"o\">=</span> <span class=\"n\">ShuffleSplit</span><span class=\"p\">(</span><span class=\"n\">n_splits</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"n\">seed</span><span class=\"p\">)</span>\n        <span class=\"k\">for</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">test_idx</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span>\n            <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">cv</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">df</span><span class=\"p\">)),</span>\n            <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n            <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"sa\">f</span><span class=\"s\">\"Seed </span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">: Folds\"</span><span class=\"p\">,</span>\n            <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">\"fold\"</span><span class=\"p\">,</span>\n        <span class=\"p\">):</span>\n            <span class=\"c1\"># Save data for training models using Chemprop CLI\n</span>            <span class=\"n\">save_data_for_chemprop</span><span class=\"p\">(</span>\n                <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">test_idx</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Chemprop model using CLI\n</span>            <span class=\"n\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"n\">task_type</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Make predictions on test set using trained Chemprop model\n</span>            <span class=\"n\">make_chemprop_predictions</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Use trained model to create fingerprints\n</span>            <span class=\"n\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"s\">\"train\"</span><span class=\"p\">)</span>\n            <span class=\"n\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"s\">\"test\"</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Scikit-learn models on Morgan fingerprints\n</span>            <span class=\"n\">morgan_rf</span> <span class=\"o\">=</span> <span class=\"n\">train_morgan_sklearn_model</span><span class=\"p\">(</span>\n                <span class=\"n\">fps_df</span><span class=\"p\">,</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">target_col</span><span class=\"p\">],</span> <span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">task_type</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Scikit-learn model on Chemprop fingerprints\n</span>            <span class=\"n\">chemprop_rf</span> <span class=\"o\">=</span> <span class=\"n\">train_chemprop_sklearn_model</span><span class=\"p\">(</span>\n                <span class=\"n\">project_name</span><span class=\"p\">,</span>\n                <span class=\"n\">seed</span><span class=\"p\">,</span>\n                <span class=\"n\">fold_number</span><span class=\"p\">,</span>\n                <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">target_col</span><span class=\"p\">],</span>\n                <span class=\"n\">train_idx</span><span class=\"p\">,</span>\n                <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Gather predictions\n</span>            <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">gather_predictions</span><span class=\"p\">(</span>\n                <span class=\"n\">morgan_rf</span><span class=\"p\">,</span>\n                <span class=\"n\">chemprop_rf</span><span class=\"p\">,</span>\n                <span class=\"n\">project_name</span><span class=\"p\">,</span>\n                <span class=\"n\">seed</span><span class=\"p\">,</span>\n                <span class=\"n\">fold_number</span><span class=\"p\">,</span>\n                <span class=\"n\">df</span><span class=\"p\">,</span>\n                <span class=\"n\">fps_df</span><span class=\"p\">,</span>\n                <span class=\"n\">test_idx</span><span class=\"p\">,</span>\n                <span class=\"n\">target_col</span><span class=\"p\">,</span>\n                <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n            <span class=\"n\">all_predictions</span><span class=\"p\">.</span><span class=\"n\">update</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">all_predictions</span>\n</code></pre></div></div>\n<p>The next two cells run the training and prediction process for the classification and regression datasets.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Set up workspace\n</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"models\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">seed</span><span class=\"p\">(</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"n\">n_seeds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">n_folds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">test_frac</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span>\n<span class=\"n\">data_path</span> <span class=\"o\">=</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/raw/bsep.csv\"</span><span class=\"p\">)</span>\n<span class=\"n\">project_name</span> <span class=\"o\">=</span> <span class=\"s\">\"bsep\"</span>\n<span class=\"n\">smiles_col</span> <span class=\"o\">=</span> <span class=\"s\">\"smiles\"</span>\n<span class=\"n\">target_col</span> <span class=\"o\">=</span> <span class=\"s\">\"bsep\"</span>\n<span class=\"n\">task_type</span> <span class=\"o\">=</span> <span class=\"s\">\"classification\"</span>\n\n<span class=\"n\">classification_predictions</span> <span class=\"o\">=</span> <span class=\"n\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Seeds:   0%|          | 0/5 [00:00<?, ?seed/s]\nSeed 0: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 1: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 2: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 3: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 4: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\n</code></code></pre></div></div>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">n_seeds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">n_folds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">test_frac</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span>\n<span class=\"n\">data_path</span> <span class=\"o\">=</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/raw/biogen_logS.csv\"</span><span class=\"p\">)</span>\n<span class=\"n\">project_name</span> <span class=\"o\">=</span> <span class=\"s\">\"logS\"</span>\n<span class=\"n\">smiles_col</span> <span class=\"o\">=</span> <span class=\"s\">\"SMILES\"</span>\n<span class=\"n\">target_col</span> <span class=\"o\">=</span> <span class=\"s\">\"logS\"</span>\n<span class=\"n\">task_type</span> <span class=\"o\">=</span> <span class=\"s\">\"regression\"</span>\n\n<span class=\"n\">regression_predictions</span> <span class=\"o\">=</span> <span class=\"n\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Seeds:   0%|          | 0/5 [00:00<?, ?seed/s]\nSeed 0: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 1: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 2: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 3: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 4: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\n</code></code></pre></div></div>\n<p>The next function takes the predicted values and true values from the test set and calculates a relevant performance metric.</p>\n<p>For the moderately imbalanced BSEP classification data (17% positive class), I used the Matthews Correlation Coefficient (MCC). MCC is a value between -1 and 1 and is a robust metric for imbalanced datasets. For the LogS regression data, I used the coefficient of determination (R2). An R2 value of 1 indicates perfect agreement between predicted and true values, with no lower limit for poor predictions.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calculate_performance</span><span class=\"p\">(</span><span class=\"n\">preds_dict</span><span class=\"p\">:</span> <span class=\"nb\">dict</span><span class=\"p\">,</span> <span class=\"n\">metric</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Analyse performance of models.\n\n    Args:\n        preds_dict (dict): Dictionary containing predictions from all models.\n        metric (Callable): Metric function to evaluate performance.\n\n    Returns:\n        dict: Dictionary containing performance metrics for each model.\n    \"\"\"</span>\n    <span class=\"n\">metrics</span> <span class=\"o\">=</span> <span class=\"n\">defaultdict</span><span class=\"p\">(</span><span class=\"nb\">list</span><span class=\"p\">)</span>\n    <span class=\"k\">for</span> <span class=\"n\">predictions</span> <span class=\"ow\">in</span> <span class=\"n\">preds_dict</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">():</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"rf\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"chemprop_rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"chemprop_rf\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"chemprop_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"chemprop\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">metrics</span>\n\n\n<span class=\"n\">classification_metrics</span> <span class=\"o\">=</span> <span class=\"n\">calculate_performance</span><span class=\"p\">(</span>\n    <span class=\"n\">classification_predictions</span><span class=\"p\">,</span> <span class=\"n\">matthews_corrcoef</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">regression_metrics</span> <span class=\"o\">=</span> <span class=\"n\">calculate_performance</span><span class=\"p\">(</span><span class=\"n\">regression_predictions</span><span class=\"p\">,</span> <span class=\"n\">r2_score</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>To visualise and inspect the classification results, I've used confusion matricies. All three models have a significant number of True Negatives (TN) in the top left square. The Random Forest model and the Chemprop model have a significant number of False Negatives (FN), whereas the Chemprop-RF model has more True Positives (TP) and more False Positives (FP).</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">confusion_matricies</span><span class=\"p\">(</span><span class=\"n\">classification_predictions</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Confusion matrices for the Random Forest, Chemprop, and Chemprop-RF classification models\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-1.png\"/></p>\n<p>To visualise and inspect the regression results, I've used x-y scatter plots of the predicted values against the true values. The Random Forest regressor has poor predictions, with more values being overestimated. The Chemprop model and the Chemprop-RF models perform better, especially on these lower LogS values. Some performance metrics are annotated on the plots in the top left: R2, RMSE (root mean squared error), and MAE (mean absolute error).</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">scatter_plots</span><span class=\"p\">(</span><span class=\"n\">regression_predictions</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Predicted versus true value scatter plots for the Random Forest, Chemprop, and Chemprop-RF regression models, annotated with R2, RMSE, and MAE\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-2.png\"/></p>\n<p>As recommended in the paper I've linked, I used one-way ANOVA to compare the performance distributions from the 5x5 cross-validation and determine if there was a statistically significant difference between the means. This was followed by Tukey's Honest Significant Differences as a post-hoc pairwise test. The results are plotted using the Simultaneous Confidence Interval plot from the statsmodels Python library.</p>\n<p>For both the classification and regression datasets, the ANOVA results showed a statistically significant difference in model performance (p&lt;0.05).</p>\n<p>For the classification dataset, post-hoc tests revealed a statistically significant difference in performance between all pairs of models, with the Chemprop-RF model performing the best.</p>\n<p>For the regression dataset, the Random Forest model performed statistically significantly worse than the other two. However, there was no statistically significant difference between the performance of the Chemprop and Chemprop-RF models.</p>\n<p>The classification dataset (BSEP, 807 entries) is much smaller than the regression dataset (LogS, 2,173 entries). This might suggest that Chemprop-RF models are particularly effective for low-data problems, where there isn't enough data to train a high-performing Feed-Forward Neural Network (FFN) alone.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Adapted from https://github.com/PatWalters/practical_cheminformatics_posts/blob/main/adme_comparison/\n</span><span class=\"k\">def</span> <span class=\"nf\">run_anova</span><span class=\"p\">(</span><span class=\"n\">df_in</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">float</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run one-way ANOVA on model performance.\"\"\"</span>\n    <span class=\"n\">res_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n    <span class=\"k\">for</span> <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">v</span> <span class=\"ow\">in</span> <span class=\"n\">df_in</span><span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"method\"</span><span class=\"p\">):</span>\n        <span class=\"n\">res_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">v</span><span class=\"p\">[</span><span class=\"n\">col</span><span class=\"p\">].</span><span class=\"n\">values</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">f_oneway</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">res_list</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">metric_dict</span><span class=\"p\">:</span> <span class=\"nb\">dict</span><span class=\"p\">,</span> <span class=\"n\">metric</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Plot comparison of model performance using a specified metric.\n\n    Args:\n        metric_dict (dict): Dictionary containing performance metrics for each model.\n        metric (Callable): Metric function used to evaluate performance.\n    \"\"\"</span>\n    <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">ax</span> <span class=\"o\">=</span> <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">subplots</span><span class=\"p\">()</span>\n    <span class=\"n\">melt_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">metric_dict</span><span class=\"p\">).</span><span class=\"n\">melt</span><span class=\"p\">()</span>\n    <span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"method\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">melt_df</span><span class=\"p\">.</span><span class=\"n\">variable</span><span class=\"p\">.</span><span class=\"nb\">map</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Random Forest\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"chemprop_rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Chemprop-RF\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"chemprop_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">best_model</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">melt_df</span><span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"method\"</span><span class=\"p\">)[</span><span class=\"s\">\"value\"</span><span class=\"p\">]</span>\n        <span class=\"p\">.</span><span class=\"n\">mean</span><span class=\"p\">()</span>\n        <span class=\"p\">.</span><span class=\"n\">reset_index</span><span class=\"p\">()</span>\n        <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"value\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">method</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">tukey</span> <span class=\"o\">=</span> <span class=\"n\">pairwise_tukeyhsd</span><span class=\"p\">(</span>\n        <span class=\"n\">endog</span><span class=\"o\">=</span><span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"value\"</span><span class=\"p\">],</span> <span class=\"n\">groups</span><span class=\"o\">=</span><span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"method\"</span><span class=\"p\">],</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.05</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">tukey</span><span class=\"p\">.</span><span class=\"n\">plot_simultaneous</span><span class=\"p\">(</span><span class=\"n\">comparison_name</span><span class=\"o\">=</span><span class=\"n\">best_model</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">,</span> <span class=\"n\">figsize</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">8</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">))</span>\n    <span class=\"n\">anova_p_value</span> <span class=\"o\">=</span> <span class=\"n\">run_anova</span><span class=\"p\">(</span><span class=\"n\">melt_df</span><span class=\"p\">,</span> <span class=\"s\">\"value\"</span><span class=\"p\">)</span>\n    <span class=\"n\">ax</span><span class=\"p\">.</span><span class=\"n\">set_title</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">'_'</span><span class=\"p\">,</span> <span class=\"s\">' '</span><span class=\"p\">).</span><span class=\"n\">title</span><span class=\"p\">()</span><span class=\"si\">}</span><span class=\"s\"> \"</span> <span class=\"o\">+</span> <span class=\"sa\">f</span><span class=\"s\">\"ANOVA p=</span><span class=\"si\">{</span><span class=\"n\">anova_p_value</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">3</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ax</span><span class=\"p\">.</span><span class=\"n\">set_xlabel</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">'_'</span><span class=\"p\">,</span> <span class=\"s\">' '</span><span class=\"p\">).</span><span class=\"n\">title</span><span class=\"p\">()</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n\n    <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">tight_layout</span><span class=\"p\">()</span>\n    <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n\n\n<span class=\"n\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">classification_metrics</span><span class=\"p\">,</span> <span class=\"n\">matthews_corrcoef</span><span class=\"p\">)</span>\n<span class=\"n\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">regression_metrics</span><span class=\"p\">,</span> <span class=\"n\">r2_score</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Tukey HSD simultaneous confidence intervals comparing Matthews correlation coefficient across the three classification models, titled with the ANOVA p-value\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-3.png\"/></p>\n<p><img alt=\"Tukey HSD simultaneous confidence intervals comparing R2 across the three regression models, titled with the ANOVA p-value\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-4.png\"/></p>","doi":"https://doi.org/10.59350/gsppn-fg196","guid":"https://jonswain.github.io/chemprop-rf","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1757635200,"rid":"68c9f-7tb53","summary":"Whilst neural networks ( <strong> NNs </strong> ) have done amazing things with unstructured data such as text and images, they've traditionally been outperformed on tabular data by Gradient-Boosted Decision Trees ( <strong> GBDTs </strong> ), although recent advances such as TabPFN and TabICL suggest that the performance gap may have closed. I've written a bit about TabPFN for chemical datasets here.","tags":["Ai","Cheminformatics","Data-science","Machine-learning"],"title":"Chemprop-RF: A Hybrid Approach to Chemical Property Prediction","updated_at":1788763518,"url":"https://jonswain.github.io/chemprop-rf-hybrid-chemical-property-prediction/","version":"v1"}}],"items":[{"authors":[{"affiliation":[{"name":"DataCite"}],"contributor_roles":[],"family":"Cousijn","given":"Helena","url":"https://orcid.org/0000-0001-6660-6214"}],"blog":{"authors":[{"name":"DataCite Staff"}],"community_id":"916f4925-a9f6-4b4d-b823-c769ef054f15","created":1733529600,"current_feed_url":null,"description":"Connecting Research, Advancing Knowledge","doi":null,"favicon":"https://rogue-scholar.org/api/communities/916f4925-a9f6-4b4d-b823-c769ef054f15/logo","feed_format":"application/atom+xml","feed_url":"https://datacite.org/blog/feed/atom/","filter":null,"generator":"WordPress","home_page_url":"https://datacite.org/blog/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":null,"relative_url":null,"secure":true,"slug":"datacite","status":"active","subfield":"1710","title":"DataCite Blog - DataCite","updated":1787828838,"use_api":false},"blog_name":"DataCite Blog - DataCite","blog_slug":"datacite","content_html":"<p>As a membership organization, members are at the core of everything we do. Understanding our members and ensuring good 2-way communication is therefore extremely important. Over the last year, we have been working on clarifying and aligning DataCite's membership model. We believe this will enable us to work with all organizations in the same way and give us more insight into the organizations using our services.</p>\n<p><strong>Types of Membership </strong></p>\n<p>Previously, we distinguished between Members, Providers, Clients, and Consortia. This often led to confusion among organizations about whether or not they are a DataCite Member. Therefore, going forward, we distinguish three clear membership categories: Member-only, Direct Member, and Consortium Member. The Member-only category remains unchanged and consists of Members that support DataCite's data sharing mission, want to collaborate with DataCite and/or be part of DataCite's governance. These Members do not register DOIs.</p>\n<p>When it comes to Members that do consume DOI services, we distinguish two Member types: the Direct Member and the Consortium Member.</p>\n<p>Direct Member</p>\n<p>This type of Member is a single organization that joins DataCite as a Member in its own right and consumes DOI services. This single organization may have one or more repositories under their umbrella, but those repositories are under the same administrative structure as the organization. A Direct Member also takes on membership responsibilities such as participating in DataCite's governance.</p>\n<p><img decoding=\"async\" src=\"http://datacite.org/wp-content/uploads/2021/11/direct_member.jpg\"/></p>\n<p>Consortium Member</p>\n<p>A consortium is a group of like-minded organizations that have come together to collectively participate in DataCite's community and governance activities and to use DataCite's DOI services. A consortium is composed of two or more non-profit organizations that are under different administrative structures. Consortia are generally located in a single country or subject-based. Organizations within a consortium can work with one or more repositories that are under the same administrative structure as that organization.</p>\n<p>The entire consortium is a Member collectively, meaning that the consortium has a single representative in DataCite's governance structure, regardless of the number of organizations that make up the consortium. One organization will take on the role of Consortium Lead, with the others participating as Consortium Organizations.</p>\n<p><img decoding=\"async\" src=\"http://datacite.org/wp-content/uploads/2021/11/consortium_member.jpg\"/></p>\n<p>You can find more information about the different types of membership on our <a href=\"https://datacite.org/become.html\">membership page</a>.</p>\n<p><strong>Repositories </strong></p>\n<p>Repositories play a key role in DataCite services and are therefore a key component of the DataCite membership model. We define a repository as a service operated by research organizations, where research materials are stored, managed, and made accessible. A repository is a single unit and DataCite links the repository to information in <a href=\"https://www.re3data.org/\" rel=\"noopener noreferrer\" target=\"_blank\">re3data</a>, where additional repository metadata are available.</p>\n<p>However, we realize that not all content is hosted in repositories and therefore DataCite also includes periodicals as a designation. The term periodicals include journals, proceedings, books, blogs, and working paper series.</p>\n<p>The repository information provided will be used and displayed throughout DataCite services including search, data usage statistics, Member DOI statistics, and third-party indexing services.</p>\n<p><strong>DOI Fabrica </strong></p>\n<p>Going forward, we'll be making some changes to DOI Fabrica to align with the membership categories described above.</p>\n<p>Repositories</p>\n<p>We'll be replacing the old \"Clients\" terminology with \"Repositories\" and making a stronger connection to the repository concept by adding metadata elements from re3data to the repository settings information displayed in Fabrica. Periodicals, as their own sub-category of research material service, will be designated by a periodicals badge for easy identification.</p>\n<p>Consortia</p>\n<p>We'll implement the Consortium Member structure so that what Consortium Organizations can see and what they have permissions to administer in Fabrica aligns with their unique status according to the model. Implementing this properly will mean a brief transfer period (on the order of hours) for the DOIs belonging to these organizations. We'll be reaching out to our current Consortium Members and their constituent organizations over the next few weeks to confirm their repository details and schedule their transfer periods. For existing Members that are planning on forming Consortia, we will work with you to ensure that your current Clients are successfully migrated to Consortium Organizations with Repositories in Fabrica.</p>\n<p>Timeline</p>\n<p>We're planning on rolling the consortium and repository changes into the test system during the last week of August, and we plan to go live in production by mid-September. In the interim, there will be a brief period when the test system will reflect the new changes while the production system will not, so what you see in the two systems will not match. During this period we'd welcome your feedback in addition to our more formal testing.</p>\n<p>What will I see?</p>\n<ul>\n<li>If you're currently a Provider working with clients within your own organization, then you'll fall into the Direct Member category and will see your current Clients as Repositories.</li>\n<li>If you're currently a Provider working with clients outside of your organization, you've been contacted to discuss the possibility of forming a Consortium. For now, you'll see your Clients as Repositories.</li>\n<li>If you are a Consortium Lead, you have been contacted so your current Clients can be transferred to Consortium Organization status. These transfers will take place in September.</li>\n<li>If you're currently a Client, you will see yourself as Repository but your rights remain unchanged. If you are an independent organization please contact us to discuss a transfer to Direct Member or Consortium Organization status.</li>\n</ul>\n<p><strong>Member Information </strong></p>\n<p>With all these changes happening, it's critical for us that we have the correct contact information for each organization. This information is used on our <a href=\"https://datacite.org/\">Member page</a>, to administer Fabrica, to communicate with you, and for billing purposes. Therefore, we want to encourage all our Members, both our long-time Members and those going through changes, to <a href=\"https://doi.org/10.5438/q7r3-f935\" rel=\"noopener noreferrer\" target=\"_blank\">update your contact information</a> in your Member settings. We've recently added the ability to add multiple contacts per organization, based on relevant themes of interest. Please see DataCite's <a href=\"https://datacite.org/privacy.html\">privacy policy</a> to better understand how we use personal information. Expect reminders from us because at the end of the year we'll be replacing our current mailing lists with the information obtained through this form!</p>\n<p>We realize this is a lot of information and we want to support you throughout these changes in any way we can. We created an <a href=\"https://support.datacite.org/docs/general\">FAQ section</a> on our support site and are happy to talk to you at any time to find the best model for your organization. Please don't hesitate to contact support@datacite.org at any point during this process.</p>\n<p>The post <a href=\"https://datacite.org/blog/the-datacite-membership-model-consortia-repositories-and-more/\">The DataCite Membership model: consortia, repositories, and more</a> appeared first on <a href=\"https://datacite.org\">DataCite</a>.</p>","doi":"https://doi.org/10.5438/gk09-ba24","guid":"http://datacite.org/the-datacite-membership-model-consortia-repositories-and-more/","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1566777600,"rid":"405s4-00m63","summary":"As a membership organization, members are at the core of everything we do. Understanding our members and ensuring good 2-way communication is therefore extremely important. Over the last year, we [\u2026] The post The DataCite Membership model: consortia, repositories, and more appeared first on DataCite.","title":"The DataCite Membership model: consortia, repositories, and more","updated_at":1788766955,"url":"https://datacite.org/blog/the-datacite-membership-model-consortia-repositories-and-more/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Eden","given":"Terence","url":"https://orcid.org/0000-0002-9265-9069"}],"blog":{"authors":null,"community_id":"61ce553a-bafd-4aba-a952-d3bab5e85bcc","created":1788652800,"current_feed_url":null,"description":"Regular nonsense about tech and its effects \ud83d\ude43","doi":"https://doi.org/10.59350/shkspr","favicon":"https://rogue-scholar.org/api/communities/61ce553a-bafd-4aba-a952-d3bab5e85bcc/logo","feed_format":"application/atom+xml","feed_url":"https://shkspr.mobi/blog/feed/atom/","filter":null,"generator":"WordPress","home_page_url":"https://shkspr.mobi/blog","issn":"2753-1570","language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"shkspr","status":"active","subfield":"1712","title":"Terence Eden's Blog","updated":1788694460,"use_api":false},"blog_name":"Terence Eden's Blog","blog_slug":"shkspr","content_html":"<p>I imagine everyone here has received an unsolicited message telling them that their tax is overdue and that they urgently need to visit Genuine-Tax-Payment-Website.fart or that a parcel is delayed at customs and you can pay a small sum for its release at Almost-The-Right-Acronym.ak</p>\n<p>You know it is a scam. Most people just mark as spam and move on with their day. But a significant number of people don't. They hastily visit the site, tap in their credit card details, give it their mother's maiden name, confirm address, upload a nude selfie, and only then realise that they've been had.</p>\n<p>The Internet works at pretty close to the speed of light. You can register a .uk domain and a minute later it's accessible from the other side of the planet. Brilliant for users who want to quickly launch a website. Also brilliant for abusers who want to launch a spam campaign.</p>\n<p>By the time enough people have reported the scammers' domain as suspicious, it is too late. In the time it takes for a registrar to disable the domain, or for its name to make its way to the <a href=\"https://safebrowsing.google.com/\">Safe Browsing List</a>, a million messages have already been sent and enough people have handed over their details.</p>\n<p>We're told that \"<a href=\"https://en.wikipedia.org/wiki/The_purpose_of_a_system_is_what_it_does\">the purpose of a system is what it does</a>\". At the moment, the Domain Name System's purpose seems to be a vector for criminals to run scams on people at a terrifyingly high rate.</p>\n<h2 id=\"how-big-is-this-problem\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#how-big-is-this-problem\">How big is this problem?</a></h2>\n<p>BIG!</p>\n<p>There's a great blog post by Andrew Campling which reports on this startling claim:</p>\n<blockquote><p>The study found that at least 10% of all new gTLD domain names registered during the year had subsequently appeared on security blocklists by the time of analysis. It estimated that, taking account of subsequent blocklisting and associated domains not themselves blocklisted, the share of names registered by malicious actors <strong>may be closer to 20%</strong>.</p>\n<p><a href=\"https://labs.ripe.net/author/andrew_campling/dns-abuse-and-criminal-infrastructure-beyond-definitions-and-blocklists/\">DNS Abuse and Criminal Infrastructure: Beyond Definitions and Blocklists</a> (emphasis added)</p></blockquote>\n<p>That links to a presentation by Interisle which contains some rather shocking statistics (<a href=\"https://www.icann.org/en/blogs/details/looking-beyond-the-numbers-understanding-malicious-domain-registration-data-10-08-2026-en\">albeit with disputed methodology</a>). It looks at <em>generic</em> Top Level Domains (gTLD) - those are things like .com and .fun rather than country code TLDs (ccTLD) like .uk and .de.</p>\n<p>It says 85 million new registrations of gTLDs were made in 2025. Of those 8.5 million were added to blocklists by May 2025. It reckons that a 10% abuse rate is the likely floor for these numbers and it's probably closer to 20%. One in five newly registered domains with a gTLD are scams. That's a bloody crisis.</p>\n<p>13 TLDs had more than 50% of their registrations blocklisted.</p>\n<img alt=\"Table listing the top 13 generic Top-Level Domains (gTLDs) with the highest percentage of blocklisted, malicious new domains created in 2025. Ranked from highest to lowest blocklist percentage, top entries include .LOCKER (72.9%), .LGBT (72.2%), and .TOWN (70.2%). The table detail includes TLD operators, registration totals, and specific malicious domain metrics.\" class=\"aligncenter\" height=\"954\" src=\"https://shkspr.mobi/blog/wp-content/uploads/2026/08/gTLDs.webp\" width=\"1162\"/>\n<p>I can understand why .bid and .loan are popular with scammers. But why .mobi?! What did I ever do to you, eh?</p>\n<p>Who are the scammers registering these through?</p>\n<img alt=\"List of registrars. NameCheap, Gname, Dynadot, NameSilo, GoDaddy.\" class=\"aligncenter\" height=\"390\" src=\"https://shkspr.mobi/blog/wp-content/uploads/2026/08/registrars.webp\" width=\"910\"/>\n<p>Ah, our old friends at NameCheap. See <a href=\"https://shkspr.mobi/blog/2021/05/why-do-scammers-love-namecheap/\">Why do scammers love NameCheap?</a></p>\n<p>If those five registrars had more effective policies, it might significantly dent the scammers' ability to ply their devious wares. Or they might just move on to other registrars.</p>\n<p>As the report points out:</p>\n<blockquote><p>suspension rates for blocklisted domains were 7.4% to 16.3%.</p></blockquote>\n<p><a href=\"https://interisle.net/s/FullReport_MaliciousRegistrationsintheDomainNameMarket_2026_rev.pdf\">The full report is on the Interisle website</a>.</p>\n<h2 id=\"what-can-be-done\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#what-can-be-done\">What can be done?</a></h2>\n<p>I don't know.</p>\n<p>In the first instance, it might make sense for registrars to do strong Know Your Customer (KYC) checks on anyone buying a domain. But that stops anyone who wants to anonymously register <code>I-Hate-Nintendo.whatever</code> without risking the wrath of Intellectual Property lawyers.</p>\n<p>Also, criminals have access to stolen money and stolen cards. They can convince a hapless mule to register a domain on the criminals' behalf.</p>\n<p>Registrars could ask for an escrow payment. Pay \u20ac9 for the domain name put \u20ac900 in escrow. If your domain appears on a blocklist within the year, you forfeit the money. Criminals with stolen funds are unlikely to care but it would probably put off lots of people from getting a new domain.</p>\n<p>There are various banned words and phrases depending on the TLD. For example, <a href=\"https://shkspr.mobi/blog/2024/07/ss-tld-opening-for-direct-registrations/\">South Sudan</a> has a list of political words which they don't want associated with their .ss ccTLD.</p>\n<p>But if one gTLD bans a word, a different one might not. A scammer doesn't care if the gTLD is .arse or .elbow - they just want the start of the domain to look legitimate.</p>\n<p>Some registrars have strings that they don't allow. In fairness to NameCheap, when I tried to register <code>dwp-payments-gov-uk.pizza</code> it told me that domain was banned. It wouldn't let me get any gTLD with that name.</p>\n<p>But all it takes is one registrar to be slightly lax and the scammers get through. Increasing the complexity of the rules is also a hell of a burden on smaller registrars.</p>\n<p>Besides, it's pretty easy to get a generic enough looking domain and stick the confusing bit on a subdomain. Here are a clutch mentioned in the report:</p>\n<ul>\n<li><code>https://gov.uk-dwpaph.bond/uk/</code></li>\n<li><code>https://gov.uk-dwpcjh.bond/uk/</code></li>\n<li><code>https://gov.uk-dwpclc.bond/uk</code></li>\n<li><code>https://gov.uk-dwpclw.bond/uk</code></li>\n<li><code>https://gov.uk-dwpclj.bond/uk/</code></li>\n</ul>\n<p>Perhaps there ought to be a delay before a new domain goes live to allow people to object to it? That would give governments, banks, delivery companies, and a dozen more \"important\" organisations a right to veto any \"dodgy\" looking domain.</p>\n<p>But suppose someone wants to register <code>gov-uk-stole-my-horse.horse</code> to protest the government's cruel policy of stealing horses - is that a legitimate use of a domain? What if the Darwin Pensioner Divas - a group of elderly singers - want to take payments for their new album of goth/punk covers, can the DPD delivery company veto <code>dpd-payments.music</code>?</p>\n<p>Do we want a domain name system where powerful companies control exactly which domains we can register? If I have an idea for a domain on a Friday night do I have to wait until Monday before it can be launched? Are those companies realistically able to parse millions of domains per year and have a low false-positive rate?</p>\n<p>All of these things are possible - but all of them come with an impact on legitimate users. To be clear, I don't know what the right answer is.</p>\n<h2 id=\"what-is-icann-doing-about-it\"><a href=\"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/#what-is-icann-doing-about-it\">What is ICANN doing about it?</a></h2>\n<p>Lots! It has been a few years since I've been to an ICANN meeting, but even back then the topic of abuse was high on the agenda. They appear to be looking at ways to coordinate abuse reports between various entities, along with some other policies which should hopefully work.</p>\n<p>There are two salient points from <a href=\"https://hosted-files.sched.co/icann86/b3/TRANSC_I86SQV_Mon08June2026__GNSO-DNS%20Abuse%20Mitigation%20PDP%201%20%281%20of%204%29-en.pdf\">one of the discussions held at the recent meeting</a></p>\n<blockquote><p>If anybody thinks that in our current age of AI and as we move into different kinds of computing, DNS abuse is going to numerically stay steady and we will have a downward effect on that baseline 2027 number. I'm not sure that that's an accurate assumption. I think it's going to be the other thing, which is [\u2026] it's going to be easier to abuse the DNS.</p></blockquote>\n<p>And</p>\n<blockquote><p>Abusers are going to abuse because it's just too lucrative, because no matter what we do, they will find the way to make profit off of that, and will try to circumvent everything that we do. That is not a reason not to do it, though.</p></blockquote>\n<p>Quite!</p>\n<p>As I said, I don't know the answer to this. What I do know is, much like <a href=\"https://shkspr.mobi/blog/2025/08/is-it-possible-to-allow-sideloading-and-keep-users-safe/\">Android's app ecosystem being a haven for scammers</a>, DNS is facing a crisis. When trust in a system goes, only chaos follows.</p>\n<p>I don't want to live in a world where I have to show my passport and pay thousands of pounds to register a domain which is only available after being vetted by private interests. But I also don't want to live in a world where scammers have effectively no deterrent from abusing millions of people.</p>\n<p>The purpose of a system is what it does. I hope DNS's purpose can become less dangerous while still remaining open.</p>\n<img alt=\"\" height=\"1\" loading=\"eager\" src=\"https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=74588&amp;HTTP_REFERER=Atom\" width=\"1\"/>","doi":"https://doi.org/10.59350/395ha-fss97","guid":"https://shkspr.mobi/blog/?p=74588","image":"https://shkspr.mobi/blog/wp-content/uploads/2026/08/gTLDs.webp","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1788652800,"rid":"8hyr0-9p774","summary":"I imagine everyone here has received an unsolicited message telling them that their tax is overdue and that they urgently need to visit Genuine-Tax-Payment-Website.fart or that a parcel is delayed at customs and you can pay a small sum for its release at Almost-The-Right-Acronym.ak You know it is a scam. Most people just mark as spam and move on with their day.","tags":["/etc/","ICANN","Internet","Scam","Spam"],"title":"The purpose of DNS is to spread scams","updated_at":1788766212,"url":"https://shkspr.mobi/blog/2026/09/the-purpose-of-dns-is-to-spread-scams/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p><img alt=\"A triptych of a research chemist retraining as a data scientist, generated by Dall E 3\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/career_change/chemistry_to_data_science.png\"/></p>\n<hr/>\n<h2 id=\"seedling-time-for-a-change\">:seedling: Time for a change</h2>\n<p>In 2020 I was working as a synthetic chemist in a laboratory in Aotearoa (New Zealand). I was getting disillusioned with the work (and academia in general) and started looking for a new career direction and new challenges. After chatting with a few friends who were working as data scientists I realised that my current interests in scientific computing, statistics, and drug discovery would transfer nicely to a job as a data scientist or cheminformatician.</p>\n<p>It felt like an emerging field within biomedical research, with huge potential. Alphafold 2 had just been released and was causing a lot of excitement, but the LLM hype was still yet to fully start (ChatGPT was still a few years away). It seemed like a perfect time to make the jump to a new, exciting field that would grow in the future.</p>\n<p>When I started searching for information about making the switch I somehow felt both completely lost and unguided, and overwhelmed by the resources out there. Part of the thinking behind this post was: What do I wish I knew before setting out on this journey?</p>\n<p>I've been asked a couple of times about making the change, mostly by old colleagues looking to move from chemistry research to data science, but I hope this advice is useful whatever your background is. I'm always happy to answer questions but thought it would be useful to have something to point people to that contained my main ideas and could be used as a starting point for further questions.</p>\n<p>There's a huge amount resources out there, and no right or wrong way to make the change. This just lists my experience and what worked for me, this will be different for everyone. One of my favourite things about data science is the low bar for entry, there's so much open-source software and data, YouTube videos, and Kaggle competitions that you only really need a basic computer to get started. On top of this I realise I was also lucky to have friends and colleagues that I could ask for advice, and a supportive family who were willing to house me whilst I re-trained. Through 2020 and 2021 we were occasionally confined at home due to COVID lockdowns (though thankfully in NZ these were fairly limited compared to the rest of the world), and with not much else going on this was an idea time for some re-training.</p>\n<hr/>\n<h2 id=\"snake-learning-python\">:snake: Learning Python</h2>\n<p>As I asked around my network for advice on making this change, the first suggestion was always the same, <strong>learn to code in Python</strong>. So much data science work is done using Python libraries (pandas, numpy, scikit-learn, pytorch, and rdkit for chemistry). I now spend a significant part of each day writing and reading Python code and documentation.</p>\n<p>I did what I often do (probably a bit too much) and scrolled reddit. I kept seeing two online Python courses recommended. Automate the Boring Stuff with Python Programming by Al Sweigart, and 100 Days of Code: The Complete Python Pro Bootcamp by Dr. Angela Yu. Both were available on <a href=\"https://www.udemy.com\">Udemy</a>, which seems to regularly have special offers and I completed both courses for around $10 (US) each. There are definitely similar courses available for free so it's a personal preference what to go for. I chose the paid courses for two reasons:</p>\n<ol>\n<li>I generally found them to be more organised with a clear progression, including problem sets, projects, and with good coverage of all the important topics.</li>\n<li>With a free course it's easy to stop, especially if something comes up that stops you doing it for a few days and you get out of the habit (I can be pretty lazy). By paying for the course (even a fairly low cost of $10) was enough to manipulate myself into finishing (I'm getting my money worth).</li>\n</ol>\n<h3 id=\"automate-the-boring-stuff\">Automate the boring stuff</h3>\n<p>The first course I completed was <a href=\"https://www.udemy.com/course/automate/\">Automate the Boring Stuff with Python Programming by Al Sweigart</a>. It's a fairly short course but very well taught, designed for anyone who works regularly with a computer and wants to automate repetitive tasks, so they never have to do them again. It doesn't go too deep into theory or style and gets stuck in with practical applications quickly. Within a couple of days, I was building small programs and could start to see how I could use it for to automate parts of my job. Before starting I had no idea if I would even enjoy programming in Python, and this was an ideal introduction (turns out I did enjoy it) and gave me enough understanding to look at other courses that might be useful. It seemed perfect to make my current job less boring, but not enough for the new job I wanted, so I started looking for something more in-depth.</p>\n<h3 id=\"100-days-of-code\">100 days of code</h3>\n<p>The next course I completed was <a href=\"https://www.udemy.com/course/100-days-of-code/\">100 Days of Code: The Complete Python Pro Bootcamp by Dr. Angela Yu</a>. This is a much more in-depth course that covers a huge range of applications for Python. Every day has a couple of hours of lectures that cover theory or usage, and a few problem sets to apply what you've learnt. Every 10 days there's a small project that uses everything you've learnt, and at the end there's about 20 days of projects to construct a personal Python portfolio. I found some sections a little confusing (the first time OOP is introduced), and there's a large section borrowed from a course on web development that I felt dragged on a bit (it's useful to understand HTML, CSS, and JS for things like web-scraping, but I didn't need quite that much detail on building websites). There's a significant section towards the end on data science that was particularly useful. Overall, this course probably made the biggest difference in my re-training.</p>\n<h3 id=\"other-resources\">Other resources</h3>\n<ul>\n<li><a href=\"https://cs50.harvard.edu/python/2022/\">CS50 Python from Harvard University</a>: I haven't completed this but if the quality is comparable to CS50 and CS50 SQL (discussed below) it'll be a very good course for learning Python. David Malan is a fantastic lecturer and each week there's problem sets. It's free to complete online, but you could pay for a certificate if you wanted to.</li>\n<li><a href=\"https://www.codewars.com/\">Codewars</a>: Lots of coding problems with a range of difficulties. I used these at the start to practice my Python programming, and still occasionally use the SQL problems to brush up as I don't use SQL regularly at my job.</li>\n<li><a href=\"https://adventofcode.com\">Advent of Code</a>: A daily problem during advent that gets progressively more difficult. Can be completed in any programming language.</li>\n<li><a href=\"https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/\">Fluent Python</a>: For a more advanced understanding of Python</li>\n</ul>\n<hr/>\n<h2 id=\"bar_chart-data-science-experience\">:bar_chart: Data science experience</h2>\n<p><img alt=\"I can't get a job because I don't have experience because I can't get a job\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/career_change/cant_get_job.png\"/></p>\n<p>At this stage I started thinking about jobs, but everyone I spoke to gave the same response: \"What you're doing is the right idea, but we're just looking for someone with more experience.\" This seems to be a classic problem when re-training, how do you get your first experience when everyone wants someone with experience? There are a few ways to go about this, but the main two seem to be either an internship or a bootcamp. Both have pros and cons, and I decided to go down the bootcamp route. To me the main advantages were the ease of organisation (I was relocating to the UK at the time and having something waiting for me was nice), and the shorter duration (most internships seemed to be multiple months, whereas the bootcamp I did was 5 weeks).</p>\n<h3 id=\"s2ds\">S2DS</h3>\n<p>At the recommendation of a friend, I applied to <a href=\"https://www.s2ds.org\">Science to Data Science (S2DS)</a>, a 5-week, intensive, project-based bootcamp for research scientists with a PhD or MSc looking to move into data science. The application involved a short technical project to test your exploratory data analysis skills and a behavioural interview (that seemed mostly to filter out psychopaths).</p>\n<p>For the 5 weeks you're part of a small group that gets paired with a company, working on a data science project. There's a couple of talks in the first week about best practices, but you're working on your project straight away. My team was paired with Deutsche Welle (DW), a German broadcaster. They were interested in the gender breakdown of people mentioned and quoted in their articles, so the project involved Natural Language Processing (NLP) and Named Entity Recognition (NER), as well as using techniques like web-scraping for data gathering and machine learning classifiers for predicting gender from the name.</p>\n<p>Working in a team made the experience a lot of fun. We all came from a range of backgrounds and had different skillsets, but all started the bootcamp from similar positions so were learning together. It also gave a first experience using git and github collaboratively. In addition to the team, we had a mentor from S2DS who helped with problems, and contacts with DW who helped with project direction.</p>\n<p>When I applied S2DS was \u00a3800 for the entire course, not cheap, but much cheaper than many bootcamps I saw advertised online. I made the calculation that getting a job and starting to get paid more quickly would pay off in the long run. I've heard bad things about some bootcamps found online, so always do your research before signing up. If you've got the connections or are willing to put the work in organising it yourself, an internship is another great way to gain experience without the up-front cost.</p>\n<p>My experience with S2DS was positive, and that seems to invariably be the feeling among other alumni. Having this experience on my CV and a project to talk about in interviews was invaluable during my job hunt. If you do decide to sign up, feel free to put me as your reference, I think I get some Amazon vouchers or similar. One unique advantage from completing S2DS is the ongoing career advice and the community of alumni, which will be useful going forwards in my career.</p>\n<h3 id=\"cs50x-and-cs50-sql\">CS50x and CS50 SQL</h3>\n<p>Two other online courses I completed were <a href=\"https://cs50.harvard.edu/x/\">CS50x</a> and <a href=\"https://cs50.harvard.edu/sql/\">CS50 SQL</a> from Harvard.</p>\n<p>CS50x is Harvard's introductory computer science course. It's an 11-week course with lectures, short videos on specific subjects, and problem sets each week. Whilst not essential information for a data scientist, I feel having a much better understanding of how a computer works has made me a better data scientist. The first few weeks working through problems using C made me appreciate how easy Python is!</p>\n<p>CS50 SQL is a 7-week SQL course with the same structure that teaches you all the basics you'll need for a job in data science. Most of the 7 weeks are done using SQLite, but it later moves onto ProgreSQL and MySQL.</p>\n<h3 id=\"other-resources-and-ideas\">Other resources and ideas</h3>\n<ul>\n<li><a href=\"https://www.oreilly.com/library/view/python-for-data/9781449323592/\">Python for Data Analysis</a>: Everything you need to know about the Pandas library.</li>\n<li><a href=\"https://www.oreilly.com/library/view/practical-statistics-for/9781491952955/\">Practical Statistics for Data Scientists</a>: Since many data scientists moved into the field from other areas of research, they lack the statistical understanding required (me included), brushing on the statistical rigour required to properly understand results is a good idea.</li>\n<li><a href=\"https://www.oreilly.com/library/view/data-science-from/9781492041122/\">Data Science from Scratch</a>: A good introduction to Data Science using Python.</li>\n<li>Personal projects: A portfolio on you Github is good for showing off your ability. After watching a few lectures from the <a href=\"https://www.fast.ai\">FastAI course</a> by Jeremy Howard I decided to create a computer vision classifier for chemical compounds.</li>\n<li><a href=\"https://www.kaggle.com\">Kaggle competitions</a>: I've never actually competed in any, but they seem very popular.</li>\n</ul>\n<hr/>\n<h2 id=\"pill-cheminformatics\">:pill: Cheminformatics</h2>\n<p>With a background in chemistry research, cheminformatics seemed like a natural field for me. Giving me the opportunity to combine by experience in chemistry and drug discovery with my new skills in Python programming and data science. I looked for short online courses and textbooks to get experience with cheminformatics problems and gain an understanding of the basics.</p>\n<p>I started with the <a href=\"https://chem.libretexts.org/Courses/Intercollegiate_Courses/Cheminformatics\">Cheminformatics OLCC</a>. This is an introductory course with 8 sections that cover the basics of cheminformatics. It starts with help setting up a Python environment, and covers topics such as representing molecules, chemical databases, QSAR modelling, and simple machine learning for chemistry.</p>\n<p>I then worked through <a href=\"https://www.oreilly.com/library/view/deep-learning-for/9781492039822/\">Deep Learning for the Life Sciences</a>. As datasets in the life sciences get larger, deep learning becomes more powerful. This covers how deep learning is used on molecules, proteins, and nucleic acids, including code so you can follow along.</p>\n<p>I also attended the <a href=\"https://www.ai4science.network\">AI4SD</a> Machine Learning Summer School at the University of Southampton. This was a week of lectures on topics such as machine learning, github, and LaTeX, with a focus on applications in chemistry. The week ended with a hackathon where we worked as a team to solve a chemical property prediction problem.</p>\n<p><a href=\"https://volkamerlab.org/projects/teachopencadd/\">TeachOpenCADD</a> looks like another great resource. I've not yet had the chance to go through it in detail, but it has a wide range of Jupyter Notebooks on all sorts of cheminformatics topics.</p>\n<p>I found attending meetings and conferences on AI and cheminformatics was a great way to learn about current areas of research and meet interesting people doing similar research. This list is mostly limited to my local area (Cambridgeshire, UK).</p>\n<ul>\n<li><a href=\"http://c-inf.net/\">Cambridge Cheminformatics Network Meetings</a> run every quarter with three speakers presenting their work. Free to attend in person or virtually via zoom. There's a \"networking\" opportunity (a pub trip) afterwards.</li>\n<li><a href=\"https://ukqsar.org/index.php/category/meetings/\">UK QSAR meetings</a> are twice a year and free to attend with high quality speakers.</li>\n<li><a href=\"https://www.rscbmcs.org/events/aichem7/\">The RSC AI in Chemistry Conference</a> is a multi-day annual meeting with speakers from all over the world presenting the cutting edge of research.</li>\n<li><a href=\"https://www.milner.cam.ac.uk/ai-club/\">Cambridge AI Club for Biomedicine</a> - I've not been to this one yet, but the topics discussed at previous meetings look interesting.</li>\n<li><a href=\"https://psolsson.github.io/AI4ScienceSeminar\">Chalmers AI4Science Seminars</a> are monthly virtual meetings where early-career researchers present their work using machine learning for scientific research.</li>\n</ul>\n<p>There's a load of useful blogs and newsletters for keeping up to date with cheminformatics:</p>\n<ul>\n<li><a href=\"http://www.drugdiscovery.net/\">DrugDiscovery.NET - AI in Drug Discovery</a> - A newsletter from Andreas Bender. It contains interesting links, details of events, and job listings in cheminformatics.</li>\n<li><a href=\"https://practicalcheminformatics.blogspot.com/\">Practical Cheminformatics</a> by Pat Walters. Some useful posts for common cheminformatics projects and issues.</li>\n<li><a href=\"https://greglandrum.github.io/rdkit-blog/index.html\">The RDKit Blog</a> Greg Landrum. RDKit is probably the most useful Python library for a cheminformatician, this blog has some great tips.</li>\n<li><a href=\"https://www.cheminformania.com/\">Cheminfomania</a></li>\n<li><a href=\"https://www.blopig.com/blog/\">Oxford Protein Informatics Group</a></li>\n<li><a href=\"https://iwatobipen.wordpress.com\">Is Life Worth Living?</a></li>\n</ul>\n<p>And a few useful journals to add to your RSS feed:</p>\n<ul>\n<li><a href=\"https://jcheminf.biomedcentral.com\">Journal of Cheminformatics</a></li>\n<li><a href=\"https://onlinelibrary.wiley.com/journal/18681751\">Molecular Informatics</a></li>\n<li><a href=\"https://www.sciencedirect.com/journal/artificial-intelligence-in-the-life-sciences\">Artificial Intelligence in the Life Sciences</a></li>\n<li><a href=\"https://pubs.acs.org/journal/jcisd8\">Journal of Chemical Information and Modeling</a></li>\n<li><a href=\"https://link.springer.com/journal/10822\">Journal of Computer-Aided Molecular Design</a></li>\n</ul>\n<hr/>\n<h2 id=\"memo-summary\">:memo: Summary</h2>\n<p>When I started writing this post, I didn't expect it to be quite so long! Looking back at the process of re-training I realise how much work it took, and how lucky I was to have family to help support me through it all. I'm also incredibly glad I did it! My current job suits me so much more than the lab work I was doing before.</p>\n<p>I think the general process I went through worked well, but there are still a few changes I would make if I could do it all again. I've heard that getting your first data science job has three equally important parts:</p>\n<ol>\n<li>Your skills (e.g. Python, SQL, etc.)</li>\n<li>Your portfolio and experience (e.g. Boot-camps, personal projects, internships)</li>\n<li>Your network (Friends, colleagues, recruiters)</li>\n</ol>\n<p>In the search for my first job, I over-prioritised improving my skills, spending a lot of time doing courses. Whilst this is important for succeeding at a job, getting the job requires a wider focus. If I were to do it again, I would spend more time on personal projects to put on my github, and networking with other data scientists, asking about what problems their companies have and how I might be able to help fix them. When I finally did get my first data science job, it's no surprise it was on the recommendation of a previous colleague.</p>","doi":"https://doi.org/10.59350/qfdrh-57d64","guid":"https://jonswain.github.io/I-want-to-become-a-data-scientist-but-I-have-no-idea-where-to-start","image":"https://jonswain.github.io/images/career_change/chemistry_to_data_science.png","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1714867200,"rid":"s8tvz-zy751","summary":":seedling: Time for a change In 2020 I was working as a synthetic chemist in a laboratory in Aotearoa (New Zealand). I was getting disillusioned with the work (and academia in general) and started looking for a new career direction and new challenges.","tags":["Data-science","Cheminformatics","Career-development"],"title":"I Want to Become a Data Scientist, but I Have No Idea Where to Start\u2026","updated_at":1788763540,"url":"https://jonswain.github.io/become-a-data-scientist-starting-guide/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>This is part 1 of a planned three post series on working with large chemical libraries.\nThe notebook used to create this post and all the files can be found in <a href=\"https://github.com/jonswain/active-learning\">this github repo</a>.</p>\n<hr/>\n<h2 id=\"chemical-space\">Chemical space</h2>\n<p>Estimates of the size of <a href=\"https://en.wikipedia.org/wiki/Chemical_space\">chemical space</a> (the number of different molecules that could exist) range from 10<sup>50</sup> - 10<sup>80</sup> possible compounds, with the upper limits approaching estimates of the <a href=\"https://en.wikipedia.org/wiki/Observable_universe\">total number of atoms in the universe</a> (around 10<sup>80</sup> atoms). It's never going to be possible to make and test every possible chemical compound, even if we limit it to only <a href=\"https://en.wikipedia.org/wiki/Druglikeness\">\"drug-like\" compounds</a>. Even with fast computational methods it's not always possible to exhaustively screen virtual libraries. We need methods to find the best scoring compounds from within virtual libraries without scoring every single compound.</p>\n<p>Small companies may have hundreds of thousands to millions of physical compounds in their virtual library, and larger companies will have libraries of a few million. Virtual make-on-demand libraries, especially combinatorial libraries, are expanding into the billions of compounds (often called ultra-large libraries). <a href=\"https://enamine.net/compound-collections/real-compounds/real-space-navigator\">Enamine REAL</a> has 48 billion possible compounds. If your scoring function takes one second per compound, you could maybe screen one million compounds (but it will take 11.6 days), but a billion seconds is nearly 32 years. As the size of virtual libraries expands, it becomes impossible to screen them exhaustively.</p>\n<p>There are a range of methods for filtering the library for screening, such as similarity searching or Thompson sampling. In this post I'll discuss active learning.</p>\n<h2 id=\"active-learning\">Active learning</h2>\n<p>Active learning is a machine learning method for searching large libraries when the scoring function that is too computationally expensive to label the full library of compounds. A selection of data is labelled with the expensive scoring function, and a machine learning model is trained on these labels and used to score all compounds from within the library. The compounds with the best scores from the machine learning model are labelled using the more expensive function, and the data from this pooled and used to train a new machine learning model. This cycle is repeated until a finish criterion is met.</p>\n<p>The initial steps are:</p>\n<ol>\n<li>A random compound is selected as a reference compound.</li>\n<li>A random sample of the unlabelled data is selected and labelled using the expensive scoring function.</li>\n<li>These labelled datapoints are used to train a simple machine learning model.</li>\n</ol>\n<p>Followed then by a repeating cycle:</p>\n<ol>\n<li>The machine learning model is used to score the entire library.</li>\n<li>The compounds with the highest scores from the machine learning model are labelled using the expensive scoring function.</li>\n<li>The labels from the expensive scoring function are pooled and the machine learning model is re-trained.</li>\n</ol>\n<hr/>\n<h2 id=\"imports\">Imports</h2>\n<p>First we need to import the libraries we will be using.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">gzip</span>\n<span class=\"kn\">import</span> <span class=\"nn\">math</span>\n<span class=\"kn\">import</span> <span class=\"nn\">re</span>\n<span class=\"kn\">import</span> <span class=\"nn\">shutil</span>\n<span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">functools</span> <span class=\"kn\">import</span> <span class=\"n\">partial</span>\n<span class=\"kn\">from</span> <span class=\"nn\">itertools</span> <span class=\"kn\">import</span> <span class=\"n\">product</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">matplotlib.pyplot</span> <span class=\"k\">as</span> <span class=\"n\">plt</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">import</span> <span class=\"nn\">seaborn</span> <span class=\"k\">as</span> <span class=\"n\">sns</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">AllChem</span><span class=\"p\">,</span> <span class=\"n\">DataStructs</span><span class=\"p\">,</span> <span class=\"n\">Descriptors</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.ensemble</span> <span class=\"kn\">import</span> <span class=\"n\">RandomForestRegressor</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n</code></pre></div></div>\n<h2 id=\"expensive-scoring-function\">Expensive scoring function</h2>\n<p>The first function we will define is the expensive scoring function, this will take a list of SMILES and return a list of scores. For this example, I'm going to try find the compound from within the library with the lowest calculated Log P. This is actually a very fast calculation and can be done exhaustively, which means we can confirm if active learning is finding the lowest value.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculates the logP value for a list of compounds.\n\n    Args:\n        smiles (pd.Series): The input molecules.\n\n    Returns:\n        np.arary: The scores of the molecules.\n    \"\"\"</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">))</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">smiles</span><span class=\"p\">]</span>\n</code></pre></div></div>\n<h2 id=\"defining-some-useful-functions\">Defining some useful functions</h2>\n<p>Next, we need to define some useful functions for the active learning pipeline.</p>\n<p>The first function creates a virtual library by combining three sets of building blocks. The smi files used here were borrowed from <a href=\"https://github.com/PatWalters/TS\">Pat Walters repository on Thompson sampling</a>. The second creates the Morgan fingerprints for all compounds within the library for training the machine learning model, which is trained using the third function. The final function scores the entire library using the machine learning model.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">build_virtual_library</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Builds a virtual library by coupling building blocks from the input smi files.\n\n    Returns:\n        pd.DataFrame: A DataFrame containing the molecular objects and SMILES strings of\n        the products.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index_col</span><span class=\"o\">=</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">reaction_smarts</span> <span class=\"o\">=</span> <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n        <span class=\"n\">bb_types</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]</span>\n        <span class=\"n\">rxn</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span><span class=\"n\">reaction_smarts</span><span class=\"p\">)</span>\n\n        <span class=\"n\">building_blocks</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"n\">bb_types</span><span class=\"p\">:</span>\n            <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n                    <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n                    <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n            <span class=\"n\">building_blocks</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n\n        <span class=\"n\">total_prods</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"p\">.</span><span class=\"n\">prod</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">building_blocks</span><span class=\"p\">])</span>\n\n        <span class=\"n\">product_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">reagents</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">product</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">building_blocks</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">total_prods</span><span class=\"p\">):</span>\n            <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">reagents</span><span class=\"p\">]</span>\n            <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n            <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n                <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n                <span class=\"n\">product_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">product_list</span><span class=\"p\">,</span>\n            <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">m</span> <span class=\"ow\">in</span> <span class=\"n\">product_list</span><span class=\"p\">],</span>\n            <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">],</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">name</span> <span class=\"o\">=</span> <span class=\"s\">\"smiles\"</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">create_morgan_fingerprints</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Creates Morgan fingerprints for the input library.\n\n    Args:\n        library (pd.DataFrame): The input library.\n\n    Returns:\n        pd.DataFrame: The Morgan fingerprints of the input library.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/fingerprints.csv\"</span><span class=\"p\">).</span><span class=\"n\">set_index</span><span class=\"p\">(</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n            <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganFingerprintAsBitVect</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">,</span> <span class=\"n\">radius</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">))</span>\n            <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">)</span>\n        <span class=\"p\">]</span>\n        <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">fps</span><span class=\"p\">,</span> <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"fp_</span><span class=\"si\">{</span><span class=\"n\">x</span><span class=\"si\">}</span><span class=\"s\">\"</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">fps</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]))],</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/fingerprints.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">fps_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_ml_model</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Trains a random forest regressor model on slow scores for the input library.\n\n    Args:\n        library (pd.DataFrame): The input library.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n\n    Returns:\n        RandomForestRegressor: The trained random forest regressor model.\n    \"\"\"</span>\n    <span class=\"n\">scored</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"n\">isna</span><span class=\"p\">()]</span>\n    <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">fingerprints</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">scored</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">]</span>\n    <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">scored</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span>\n    <span class=\"n\">regressor</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">max_depth</span><span class=\"o\">=</span><span class=\"mi\">10</span><span class=\"p\">)</span>\n    <span class=\"n\">regressor</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">regressor</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">score_library</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">regressor</span><span class=\"p\">:</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">,</span>\n    <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Scores the entire library with the trained model.\n\n    Args:\n        library (pd.DataFrame): The input library.\n        regressor (RandomForestRegressor): The trained random forest regressor model.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n\n    Returns:\n        pd.DataFrame: The input library with the model scores.\n    \"\"\"</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">regressor</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n</code></pre></div></div>\n<h2 id=\"active-learning-pipeline\">Active learning pipeline</h2>\n<p>We then need to set up the active learning pipeline. This follows the steps from above:</p>\n<ol>\n<li>A random compound is selected as a reference compound.</li>\n<li>A random sample of the unlabelled data is selected and labelled using the expensive scoring function (Tanimoto similarity).</li>\n<li>These labelled datapoints are used to train a simple machine learning (random forest) regressor.</li>\n</ol>\n<p>Followed then by a repeating cycle:</p>\n<ol>\n<li>The regressor is used to score the entire library</li>\n<li>The compounds with the highest scores from the machine learning regressor are labelled using the expensive scoring function</li>\n<li>The labels from the expensive scoring function are pooled and the machine learning model is re-trained</li>\n</ol>\n<p>Because we sometimes know the best value for a scoring function, I've added an early stopping condition if the best compound is found to save time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_active_learning</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">fingerprints</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">compounds_per_round</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">number_of_rounds</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">scoring_function</span><span class=\"p\">:</span> <span class=\"nb\">callable</span><span class=\"p\">,</span>\n    <span class=\"n\">minimize</span><span class=\"p\">:</span> <span class=\"nb\">bool</span><span class=\"p\">,</span>\n    <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">|</span> <span class=\"bp\">None</span> <span class=\"o\">=</span> <span class=\"bp\">None</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Runs active learning on the virtual library.\n\n    Args:\n        library (pd.DataFrame): The input virtual library.\n        fingerprints (pd.DataFrame): The Morgan fingerprints of the input library.\n        compounds_per_round (int): Number of compounds to select per round.\n        number_of_rounds (int): Number of active learning rounds.\n        scoring_function (callable): The slow scoring function.\n        minimize (bool): Whether to minimize or maximize the slow scoring function.\n        early_stopping_value (float | None): If the slow scoring function finds a\n        compound with this value, stops the active learning.\n\n    Returns:\n        pd.DataFrame: The input library with the slow scores added.\n    \"\"\"</span>\n    <span class=\"c1\"># Select initial random sample\n</span>    <span class=\"n\">initial_sample</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"n\">compounds_per_round</span><span class=\"p\">)</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Score the initial sample\n</span>    <span class=\"n\">initial_scores</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">initial_sample</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n\n    <span class=\"c1\"># Save the slow scores\n</span>    <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">initial_sample</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">initial_scores</span>\n\n    <span class=\"c1\"># Run active learning\n</span>    <span class=\"n\">al_round</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"k\">while</span> <span class=\"n\">al_round</span> <span class=\"o\">&lt;</span> <span class=\"n\">number_of_rounds</span><span class=\"p\">:</span>\n        <span class=\"c1\"># Train the ML model\n</span>        <span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">train_ml_model</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Use the model to score the entire virtual library\n</span>        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">score_library</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">,</span> <span class=\"n\">model</span><span class=\"p\">,</span> <span class=\"n\">fingerprints</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Select the top scoring molecules with no slow scores\n</span>        <span class=\"n\">top_compounds</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n            <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"n\">isna</span><span class=\"p\">()]</span>\n            <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">minimize</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"n\">compounds_per_round</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Score the top molecules with the slow function\n</span>        <span class=\"n\">slow_scores</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n        <span class=\"c1\"># Save the slow scores\n</span>        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">slow_scores</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">top_compounds</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">al_round</span>\n\n        <span class=\"n\">al_round</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n        <span class=\"k\">if</span> <span class=\"n\">early_stopping_value</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n            <span class=\"k\">if</span> <span class=\"n\">minimize</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"nb\">min</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n                    <span class=\"k\">break</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">].</span><span class=\"nb\">max</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n                    <span class=\"k\">break</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-1-finding-the-compound-with-the-lowest-clogp\">Example 1: Finding the compound with the lowest cLogP</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Partition_coefficient\">Calculated LogP (cLogP)</a> is an important property in drug discovery. Whilst we wouldn't usually be aiming to find the minimum or maximum value in a library, it's a useful example as it can be calculated quickly enough to exhaustively screen the library to confirm we're finding the best compound. In the example below, two compounds are randomly selected from the library and their Morgan fingerprints and cLogP values are used to train a machine learning model. This model scores the entire library and the cLogP for two best scoring compounds are pooled with the earlier compounds and the process repeated. After only six rounds (12 compounds scored), the active learning algorithm can find the lowest cLogP in the library, much quicker than exhaustively screening all 132,500 compounds! The experiment was repeated ten times and the number of compounded needed is constant across the repeats. cLogP is a simple property, but it's still impressive how little data the machine learning model needed to find the best scores.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">2</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">50</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Create the virtual library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">build_virtual_library</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># Create morgan fingerprints for the library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating Morgan fingerprints\"</span><span class=\"p\">)</span>\n<span class=\"n\">fingerprints</span> <span class=\"o\">=</span> <span class=\"n\">create_morgan_fingerprints</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Find the minimum logP in the library\n</span><span class=\"n\">all_clogp_values</span> <span class=\"o\">=</span> <span class=\"n\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Minimum logP in the library: </span><span class=\"si\">{</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">calc_logp</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">),</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">notna</span><span class=\"p\">()]))</span>\n\n<span class=\"n\">ave_scored_compounds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mean</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span> <span class=\"o\">-</span> <span class=\"n\">COMPOUNDS_PER_ROUND</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Average number of scored compounds before finding lowest value: </span><span class=\"si\">{</span><span class=\"n\">ave_scored_compounds</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Creating virtual library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1000000/1000000 [01:42&lt;00:00, 9747.08it/s]\n\nCreating Morgan fingerprints\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 132500/132500 [04:11&lt;00:00, 526.70it/s]\n\nMinimum logP in the library: -5.00\nRunning active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:21&lt;00:00,  2.11s/it]\n\nAverage number of scored compounds before finding lowest value: 12.00\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-2-recovering-a-random-compound-from-within-the-library-using-tanimoto-similarity\">Example 2: Recovering a random compound from within the library using Tanimoto similarity</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Chemical_similarity\">Molecular similarity</a> is often defined as the <a href=\"https://en.wikipedia.org/wiki/Jaccard_index\">Tanimoto similarity</a> between Morgan fingerprints. In the next experiment we'll try to maximise the Tanimoto similarity to a reference molecule. The reference molecule will be randomly selected from the library, so the maximum similarity will be 1. This turned out to be a significantly more difficult problem than the previous example minimizing cLogP. The number of compounds per round and number of rounded were increased (5000 compounds screened), and even still the reference molecule was only recovered once out of the ten repeats. This was a slightly surprising result considering the machine learning models were trained using the Morgan fingerprints that were also used in the Tanimoto similarity.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">comparison_smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">],</span> <span class=\"n\">ref_smiles</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculates the Tanimoto similarity of a reference compound to a list of\n    comparison compounds.\n\n    Args:\n        comparison_smiles (list[str]): List of SMILES to compare to\n        ref_smiles (str): SMILES of the reference compound\n\n    Returns:\n        list[float]: List of similarity scores\n    \"\"\"</span>\n    <span class=\"n\">ref_mol</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">ref_smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_mols</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_smiles</span><span class=\"p\">]</span>\n    <span class=\"n\">fpgen</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">()</span>\n    <span class=\"n\">ref_fp</span> <span class=\"o\">=</span> <span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">ref_mol</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_mols</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">DataStructs</span><span class=\"p\">.</span><span class=\"n\">FingerprintSimilarity</span><span class=\"p\">(</span><span class=\"n\">ref_fp</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_fps</span><span class=\"p\">]</span>\n\n\n<span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">50</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">False</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Choose a random reference molecule\n</span>    <span class=\"n\">ref_smiles</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">).</span><span class=\"n\">index</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">partial</span><span class=\"p\">(</span><span class=\"n\">calc_similarity</span><span class=\"p\">,</span> <span class=\"n\">ref_smiles</span><span class=\"o\">=</span><span class=\"n\">ref_smiles</span><span class=\"p\">),</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">top</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()].</span><span class=\"n\">sort_values</span><span class=\"p\">(</span>\n        <span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"round\"</span><span class=\"p\">:</span> <span class=\"n\">i</span><span class=\"p\">,</span>\n            <span class=\"s\">\"reference_molecule\"</span><span class=\"p\">:</span> <span class=\"n\">ref_smiles</span><span class=\"p\">,</span>\n            <span class=\"s\">\"compounds_scored\"</span><span class=\"p\">:</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()]),</span>\n            <span class=\"s\">\"top_scorer\"</span><span class=\"p\">:</span> <span class=\"n\">top</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">],</span>\n            <span class=\"s\">\"top_score\"</span><span class=\"p\">:</span> <span class=\"n\">top</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n\n<span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">).</span><span class=\"n\">set_index</span><span class=\"p\">(</span><span class=\"s\">\"round\"</span><span class=\"p\">).</span><span class=\"n\">style</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span>\n    <span class=\"p\">{</span><span class=\"s\">\"top_score\"</span><span class=\"p\">:</span> <span class=\"s\">\"{:.3f}\"</span><span class=\"p\">}</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Running active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [22:30&lt;00:00, 135.02s/it]\n</code></pre></div></div>\n<style type=\"text/css\">\n</style>\n<table id=\"T_5ea76\">\n<thead>\n<tr>\n<th class=\"blank level0\">Round</th>\n<th class=\"col_heading level0 col0\" id=\"T_5ea76_level0_col0\">Reference compound</th>\n<th class=\"col_heading level0 col1\" id=\"T_5ea76_level0_col1\">Compounds scored</th>\n<th class=\"col_heading level0 col3\" id=\"T_5ea76_level0_col3\">Top score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<th class=\"row_heading level0 row0\" id=\"T_5ea76_level0_row0\">0</th>\n<td class=\"data row0 col0\" id=\"T_5ea76_row0_col0\">COc1ccc2c(=O)n(C(=N)NCC(=O)O)c([C@@H](CN)CO)nc2c1OC</td>\n<td class=\"data row0 col1\" id=\"T_5ea76_row0_col1\">5100</td>\n<td class=\"data row0 col3\" id=\"T_5ea76_row0_col3\">0.466</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row1\" id=\"T_5ea76_level0_row1\">1</th>\n<td class=\"data row1 col0\" id=\"T_5ea76_row1_col0\">CC(=O)OC[C@H](C(=O)O)n1c([C@H](N)CN(C)C)nc2[nH][nH]c(=O)c2c1=O</td>\n<td class=\"data row1 col1\" id=\"T_5ea76_row1_col1\">3300</td>\n<td class=\"data row1 col3\" id=\"T_5ea76_row1_col3\">1.000</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row2\" id=\"T_5ea76_level0_row2\">2</th>\n<td class=\"data row2 col0\" id=\"T_5ea76_row2_col0\">Cc1cc2nc([C@H](C)NC(=O)CN)n(CC[C@H](N)C(=O)O)c(=O)c2cc1O</td>\n<td class=\"data row2 col1\" id=\"T_5ea76_row2_col1\">5100</td>\n<td class=\"data row2 col3\" id=\"T_5ea76_row2_col3\">0.506</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row3\" id=\"T_5ea76_level0_row3\">3</th>\n<td class=\"data row3 col0\" id=\"T_5ea76_row3_col0\">Nc1nncn1Cc1nc2c(cnc3ccnn32)c(=O)n1C(=O)[C@H](N)CC(=O)O</td>\n<td class=\"data row3 col1\" id=\"T_5ea76_row3_col1\">5100</td>\n<td class=\"data row3 col3\" id=\"T_5ea76_row3_col3\">0.495</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row4\" id=\"T_5ea76_level0_row4\">4</th>\n<td class=\"data row4 col0\" id=\"T_5ea76_row4_col0\">Cn1nnc2nc(C3(O)CC(N)C3)n([C@H](CO)C(=O)O)c(=O)c21</td>\n<td class=\"data row4 col1\" id=\"T_5ea76_row4_col1\">5100</td>\n<td class=\"data row4 col3\" id=\"T_5ea76_row4_col3\">0.488</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row5\" id=\"T_5ea76_level0_row5\">5</th>\n<td class=\"data row5 col0\" id=\"T_5ea76_row5_col0\">Cn1cc2c(=O)n(CC[C@@H](N)C(=O)O)c([C@H](CO)NC(N)=O)nc2n1</td>\n<td class=\"data row5 col1\" id=\"T_5ea76_row5_col1\">5100</td>\n<td class=\"data row5 col3\" id=\"T_5ea76_row5_col3\">0.518</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row6\" id=\"T_5ea76_level0_row6\">6</th>\n<td class=\"data row6 col0\" id=\"T_5ea76_row6_col0\">COC(=O)c1ccc2c(=O)n(CC[C@@H](N)C(=O)O)c([C@@H](N)CCO)nc2c1</td>\n<td class=\"data row6 col1\" id=\"T_5ea76_row6_col1\">5100</td>\n<td class=\"data row6 col3\" id=\"T_5ea76_row6_col3\">0.449</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row7\" id=\"T_5ea76_level0_row7\">7</th>\n<td class=\"data row7 col0\" id=\"T_5ea76_row7_col0\">C[C@H](O)[C@H](N)c1nc2cnc(Cl)nc2c(=O)n1[C@H](C(=O)O)[C@@H](C)O</td>\n<td class=\"data row7 col1\" id=\"T_5ea76_row7_col1\">5100</td>\n<td class=\"data row7 col3\" id=\"T_5ea76_row7_col3\">0.434</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row8\" id=\"T_5ea76_level0_row8\">8</th>\n<td class=\"data row8 col0\" id=\"T_5ea76_row8_col0\">Cn1cnc2c(=O)n(C(=O)C[C@H](N)C(=O)O)c([C@@H](N)CO)nc21</td>\n<td class=\"data row8 col1\" id=\"T_5ea76_row8_col1\">5100</td>\n<td class=\"data row8 col3\" id=\"T_5ea76_row8_col3\">0.519</td>\n</tr>\n<tr>\n<th class=\"row_heading level0 row9\" id=\"T_5ea76_level0_row9\">9</th>\n<td class=\"data row9 col0\" id=\"T_5ea76_row9_col0\">C[C@@H](O)[C@H](N)c1nc2nc(N)ncc2c(=O)n1C(=O)NCC(=O)O</td>\n<td class=\"data row9 col1\" id=\"T_5ea76_row9_col1\">5100</td>\n<td class=\"data row9 col3\" id=\"T_5ea76_row9_col3\">0.458</td>\n</tr>\n</tbody>\n</table>\n<p>Looking at the final run, we can see that in general the maximum similarity is not increasing significantly with further rounds, apart from the occasional spike as the model finds a similar compound. This suggests that increasing the number of rounds may not increase the performance.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">lineplot</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">[[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"s\">\"scored_round\"</span><span class=\"p\">]].</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">).</span><span class=\"nb\">max</span><span class=\"p\">().</span><span class=\"n\">values</span>\n<span class=\"p\">).</span><span class=\"nb\">set</span><span class=\"p\">(</span><span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Round\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Max similarity to reference compound\"</span><span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot of max similarity across active learning rounds\" src=\"https://jonswain.github.io/images/active_learning/active_learning_13_0.png\"/>\n</p>\n<h2 id=\"why-does-al-work-well-for-clogp-but-not-for-tanimoto-similarity\">Why does AL work well for cLogP but not for Tanimoto similarity?</h2>\n<p>AL was very successful at finding the lowest cLogP value in a library but struggled to recover a randomly chosen reference molecule by trying to maximize the Tanimoto similarity. The reason behind this may be due to the underlying distribution of data within the library. By plotting the cLogP values of all compounds within the library, we can see that the data is close to being normally distributed. The machine learning model is able to learn to predict cLogP well from the data it is given. For the Tanimoto similarity, the scores for the whole library of similarity to the reference compound from the final run are plotted, which have a large positive skew. There is lots of data for predicting Tanimoto similarities between 0.1 and 0.4, but very little data for scores above 0.4. So even though the machine learning model is using the same representation as is used to calculate the Tanimoto similarity (Morgan fingerprint), it doesn't have enough information to learn what makes a high score.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">all_similarities</span> <span class=\"o\">=</span> <span class=\"n\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">(),</span> <span class=\"n\">ref_smiles</span><span class=\"p\">)</span>\n<span class=\"n\">fig</span><span class=\"p\">,</span> <span class=\"n\">ax</span> <span class=\"o\">=</span> <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">subplots</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"n\">figsize</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">12</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">))</span>\n<span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">histplot</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">,</span> <span class=\"n\">bins</span><span class=\"o\">=</span><span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span> <span class=\"n\">label</span><span class=\"o\">=</span><span class=\"s\">\"True scores\"</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n<span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"nb\">set</span><span class=\"p\">(</span><span class=\"n\">title</span><span class=\"o\">=</span><span class=\"s\">\"cLogP values for library\"</span><span class=\"p\">,</span> <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"cLogP\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Frequency\"</span><span class=\"p\">)</span>\n<span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">histplot</span><span class=\"p\">(</span><span class=\"n\">all_similarities</span><span class=\"p\">,</span> <span class=\"n\">bins</span><span class=\"o\">=</span><span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span> <span class=\"n\">label</span><span class=\"o\">=</span><span class=\"s\">\"Predicted scores\"</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">])</span>\n<span class=\"n\">ax</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">].</span><span class=\"nb\">set</span><span class=\"p\">(</span>\n    <span class=\"n\">title</span><span class=\"o\">=</span><span class=\"s\">\"Tanimoto similarity to reference compound\"</span><span class=\"p\">,</span>\n    <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Similarity\"</span><span class=\"p\">,</span>\n    <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Frequency\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot score distributions for different tasks\" src=\"https://jonswain.github.io/images/active_learning/active_learning_15_0.png\"/>\n</p>\n<h2 id=\"example-3-docking-using-smina\">Example 3: Docking using SMINA</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Docking_(molecular)\">Molecular docking</a> is a common technique used in drug discovery to predict how a small molecule ligand will bind to a protein target. This can be combined with a scoring function to estimate the binding affinity, and these scores can be used to rank virtual compounds for experimental validation. I am far from an expert on molecular docking, but here we'll use it as an example of how active learning could be used in a real drug discovery project.</p>\n<p>Docking is slow. In this example we'll use <a href=\"https://sourceforge.net/projects/smina/\">SMINA</a>, which seem to take around one minute for each compound. We're docking compounds to the SARS MPro protein and using active learning to try find the highest binding affinity by minimizing the predicted binding energy. At one minute per docking calculation, it would take 13 weeks to exhaustively screen the virtual library used here.</p>\n<p>The experiment was repeated three times, twice it improved on the docking scores of the initial random sample, and on one run it found a very good scoring compound in the initial random sample and wasn't able to improve on that. Due to running this on my laptop, I kept the number of compounds per round (five) and the number of rounds (10) low. With more computing power, it would be interesting to see how the performance of the active learning improves with increasing these values.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">dock_mols</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Dock the molecules to the SARS MPro protein and return the affinity of the best\n    pose for each.\n\n    Args:\n        smiles: list[str]: The molecules to dock\n\n    Returns:\n        list[float]: The affinities of the best pose for each input molecule\n    \"\"\"</span>\n    <span class=\"n\">mols</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">smiles</span><span class=\"p\">]</span>\n    <span class=\"n\">scores</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n\n    <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">mols</span><span class=\"p\">:</span>\n        <span class=\"k\">with</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SDWriter</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf.sdf\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">w</span><span class=\"p\">:</span>\n            <span class=\"n\">m</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">AddHs</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span>\n            <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">EmbedMultipleConfs</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">,</span> <span class=\"n\">numConfs</span><span class=\"o\">=</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"n\">numThreads</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n            <span class=\"n\">confs</span> <span class=\"o\">=</span> <span class=\"n\">m</span><span class=\"p\">.</span><span class=\"n\">GetConformers</span><span class=\"p\">()</span>\n            <span class=\"k\">for</span> <span class=\"n\">c</span> <span class=\"ow\">in</span> <span class=\"n\">confs</span><span class=\"p\">:</span>\n                <span class=\"n\">w</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">,</span> <span class=\"n\">confId</span><span class=\"o\">=</span><span class=\"n\">c</span><span class=\"p\">.</span><span class=\"n\">GetId</span><span class=\"p\">())</span>\n\n        <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n            <span class=\"p\">[</span>\n                <span class=\"s\">\"./smina.osx.12\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--exhaustiveness\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"10\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--cpu\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"10\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--seed\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"0\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"--autobox_ligand\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/ligand_only.pdb\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-r\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/protein_minus_ligand.pdb\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-l\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/tmp_conf.sdf\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"-o\"</span><span class=\"p\">,</span>\n                <span class=\"s\">\"data/docking/tmp_conf_docked.sdf.gz\"</span><span class=\"p\">,</span>\n            <span class=\"p\">],</span>\n            <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n            <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"k\">with</span> <span class=\"n\">gzip</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf.gz\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f_in</span><span class=\"p\">:</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf\"</span><span class=\"p\">,</span> <span class=\"s\">\"wb\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f_out</span><span class=\"p\">:</span>\n                <span class=\"n\">shutil</span><span class=\"p\">.</span><span class=\"n\">copyfileobj</span><span class=\"p\">(</span><span class=\"n\">f_in</span><span class=\"p\">,</span> <span class=\"n\">f_out</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># The output sdf wasn't loading so this is a workaround\n</span>        <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"data/docking/tmp_conf_docked.sdf\"</span><span class=\"p\">,</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n            <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">read</span><span class=\"p\">()</span>\n        <span class=\"n\">affinities</span> <span class=\"o\">=</span> <span class=\"n\">re</span><span class=\"p\">.</span><span class=\"n\">findall</span><span class=\"p\">(</span><span class=\"sa\">r</span><span class=\"s\">\"<minimizedaffinity>\\n(-\\d.\\d+)\"</minimizedaffinity></span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n        <span class=\"n\">affinities</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">affinities</span><span class=\"p\">]</span>\n\n        <span class=\"n\">scores</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">affinities</span><span class=\"p\">))</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">scores</span>\n\n\n<span class=\"c1\"># Active learning parameters\n</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">NUMBER_OF_ROUNDS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">3</span>\n\n<span class=\"c1\"># Run active learning\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Running active learning\"</span><span class=\"p\">)</span>\n<span class=\"n\">scored_compounds_per_run</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">cumulative_scores</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n<span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Reset library between rounds\n</span>    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"slow_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"model_scores\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n    <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"scored_round\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">NaN</span>\n\n    <span class=\"c1\"># Active learning\n</span>    <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">run_active_learning</span><span class=\"p\">(</span>\n        <span class=\"n\">library</span><span class=\"o\">=</span><span class=\"n\">library</span><span class=\"p\">,</span>\n        <span class=\"n\">fingerprints</span><span class=\"o\">=</span><span class=\"n\">fingerprints</span><span class=\"p\">,</span>\n        <span class=\"n\">compounds_per_round</span><span class=\"o\">=</span><span class=\"n\">COMPOUNDS_PER_ROUND</span><span class=\"p\">,</span>\n        <span class=\"n\">number_of_rounds</span><span class=\"o\">=</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">dock_mols</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"c1\"># Log results\n</span>    <span class=\"n\">top</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"o\">~</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"n\">isna</span><span class=\"p\">()].</span><span class=\"n\">sort_values</span><span class=\"p\">(</span>\n        <span class=\"s\">\"slow_scores\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">scored_compounds_per_run</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"nb\">min</span><span class=\"p\">())</span>\n    <span class=\"n\">cumulative_scores</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"run_</span><span class=\"si\">{</span><span class=\"n\">i</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">scored_round</span> <span class=\"o\">&lt;=</span> <span class=\"n\">j</span><span class=\"p\">].</span><span class=\"n\">slow_scores</span><span class=\"p\">.</span><span class=\"nb\">min</span><span class=\"p\">()</span>\n        <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">)</span>\n    <span class=\"p\">]</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Average lowest docking score after screening </span><span class=\"si\">{</span><span class=\"n\">COMPOUNDS_PER_ROUND</span> <span class=\"o\">*</span> <span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"si\">}</span><span class=\"s\"> compounds: </span><span class=\"si\">{</span><span class=\"nb\">sum</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">scored_compounds_per_run</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Plot the cumulative best score found in each round for the final run\n</span><span class=\"n\">sns</span><span class=\"p\">.</span><span class=\"n\">lineplot</span><span class=\"p\">(</span><span class=\"n\">cumulative_scores</span><span class=\"p\">).</span><span class=\"nb\">set</span><span class=\"p\">(</span>\n    <span class=\"n\">xlabel</span><span class=\"o\">=</span><span class=\"s\">\"Round\"</span><span class=\"p\">,</span> <span class=\"n\">ylabel</span><span class=\"o\">=</span><span class=\"s\">\"Minimum docking score\"</span><span class=\"p\">,</span> <span class=\"n\">xticks</span><span class=\"o\">=</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_ROUNDS</span><span class=\"p\">)</span>\n<span class=\"p\">)</span>\n<span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Running active learning\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [4:54:58&lt;00:00, 5899.39s/it]  \n\nAverage lowest docking score after screening 50 compounds: -9.14\n</code></pre></div></div>\n<p align=\"center\">\n<img alt=\"A plot score against round for across three different active learning runs\" src=\"https://jonswain.github.io/images/active_learning/active_learning_17_4.png\"/>\n</p>\n<h2 id=\"summary\">Summary</h2>\n<p>In this post we've discussed active learning and seen three examples of how it could be used when our virtual library has grown too large to exhaustively screen every compound.</p>\n<h3 id=\"advantages-of-al\">Advantages of AL</h3>\n<ul>\n<li>It's much faster than exhaustive searching, and often returns the best result from within the library.</li>\n<li>Can be used to minimize or maximize a scoring function.</li>\n</ul>\n<h3 id=\"disadvantages\">Disadvantages</h3>\n<ul>\n<li>It requires a fully enumerated library. This isn't an issue for the dataset used here, but for ultra-large combinatorial libraries, it will take a long time and require a lot of memory to enumerate and store all the compounds.</li>\n<li>For ultra-large libraries scoring the entire library with the machine learning model may be too expensive.</li>\n</ul>","doi":"https://doi.org/10.59350/spfma-4yc10","guid":"https://jonswain.github.io/ultra-large-libraries-part-1","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1715990400,"rid":"td357-5y165","summary":"This is part 1 of a planned three post series on working with large chemical libraries. The notebook used to create this post and all the files can be found in this github repo.","tags":["Active-learning","Ai","Cheminformatics","Data-science","Machine-learning"],"title":"Working with Large Virtual Chemical Libraries: Part 1 - Active Learning","updated_at":1788763537,"url":"https://jonswain.github.io/ultra-large-libraries-part-1/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>I've tried to visualise and compare distributions using violin plots for reports and presentations in the past, and the feedback I've got was generally\u2026 not great. When searching for better methods I came across <a href=\"https://medium.com/@alexbelengeanu/getting-started-with-raincloud-plots-in-python-2ea5c2d01c11\">this excellent blog post by Alex Belengeanu</a> on raincloud plots and I'm now a big fan.</p>\n<hr/>\n<h2 id=\"plotting-complex-distributions\">Plotting complex distributions</h2>\n<p>There's lots of methods for visualising distributions to compare different populations, and each has its own pros and cons. Histograms (often with a kernel density estimation) are good method for showing complex distributions, but it's not possible to easily compare statistics such as the mean or quantiles from the raw graph, and there's also some loss of information from binning or smoothing, which could misrepresent the data, such as making discrete values appear continuous. Box and whisker plots are great for being able to compare statistics such as means and quartiles, but they don't represent complex distributions such as bimodal data well. A jitter plot is good for showing the structure of the underlying data, but difficult to compare statistics or absolute densities of points.</p>\n<p>A violin plot with box plot combines the strengths of the two methods to visualise complex distributions with easy to read off statistics. The main downside is they're ugly (IMO). Every time I've used them in a report or presentation the discussion has veered off to whether they look more like sea creatures or medieval weapons than violins, with very little discussion of the actual data!</p>\n<p>Whilst searching for better methods I came across <a href=\"https://medium.com/@alexbelengeanu/getting-started-with-raincloud-plots-in-python-2ea5c2d01c11\">this blog post by Alex Belengeanu</a> on raincloud plots, which seem to be the best alternative.</p>\n<h2 id=\"raincloud-plots\">Raincloud plots</h2>\n<p>Raincloud plots are pretty simple, they're just a combination of half a violin plot (sometimes called a ridgeline plot), a box plot, and a jitter plot. They're intuitive to read and show distributions clearly, as well as being much more aesthetically pleasing than many alternatives. I've customised Alex Belengeanu's code to suit my needs, and it's all in a function in <a href=\"https://github.com/jonswain/raincloudplots/\">this GitHub repo</a>.</p>\n<p><img alt=\"A raincloud plot of the features in the sklearn petal dataset\" class=\"img-responsive\" src=\"https://jonswain.github.io/images/raincloudplot/petals.png\"/></p>","doi":"https://doi.org/10.59350/0sww0-n3193","guid":"https://jonswain.github.io/Displaying-distributions-with-raincloud-plots","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1730419200,"rid":"e3ae3-xpz23","summary":"I've tried to visualise and compare distributions using violin plots for reports and presentations in the past, and the feedback I've got was generally\u2026 not great. When searching for better methods I came across this excellent blog post by Alex Belengeanu on raincloud plots and I'm now a big fan.","tags":["Data-science","Visualisation"],"title":"Displaying Distributions with Raincloud Plots","updated_at":1788763532,"url":"https://jonswain.github.io/displaying-distributions-with-raincloud-plots/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>This is part 2 of a a planned three post series on working with large chemical libraries.\nThe notebook used to create this post, and all the files can be found in <a href=\"https://github.com/jonswain/ga-for-ul-libraries\">this github repo</a>.</p>\n<hr/>\n<h2 id=\"combinatorial-libraries\">Combinatorial libraries</h2>\n<p>Combinatorial libraries can grow quickly, combining three sets of 1,000 building blocks allows you to access one billion possibilities. For example, 1,000 amines, 1,000 halide functionalised carboxylic acids, and 1,000 boronic acids can be combined to make a billion-member library. That would be a lot of synthesis and a lot of testing if you wanted to screen every member of the library! Even if a computational method took 1 second to score each virtual compound, it would take 32 years to score the entire library. Make-on-demand chemical suppliers such as Enamine have libraries that contain multiple billions of compounds, often created by combining sets of building blocks using reliable chemistry. We need methods for effectively sampling this chemical space.</p>\n<p>In this post I'll be looking into using a genetic algorithm to search a small combinatorial library. These are quick and incredibly simple methods that can be used to search combinatorial libraries for the combination of building blocks that maximises or minimises a scoring function.</p>\n<p>Whilst this method uses a combinatorial library that contains all possible configurations of all three building blocks, it's possible to extend it to more complex combinatorial libraries by <a href=\"https://www.youtube.com/watch?v=lNzW6_z_jko\">storing the building blocks and reactions as a graph</a>.</p>\n<h2 id=\"genetic-algorithms\">Genetic algorithms</h2>\n<p>Genetic algorithms are biologically inspired, based on biological natural selection. They use a very simple algorithm, with no machine learning or complex statistics. One of the big advantages is that you don't need to fully enumerate of score the entire library, only certain combinations of building blocks need to be enumerated and scores, reducing memory and computational costs. Working in building block space rather than with enumerated structures means the complexity will scale with number of building blocks, which will increase much more slowly than the total library size. The downside of the genetic algorithm is that it requires combinatorial libraries and won't work with large collections of diverse molecules not made up from the same building blocks.</p>\n<p>Initially a random population of the unlabelled data is selected by randomly choosing building blocks and enumerating the reaction products from these building blocks.</p>\n<p>The genetic algorithm has a cycle that is repeated, and each round is called a generation.</p>\n<ol>\n<li>The population is labelled using the expensive scoring function.</li>\n<li>A selection pressure is applied to the population (the lowest scoring compounds in the population are removed).</li>\n<li>A new population is created by randomly shuffling the building blocks from the surviving population (mating).</li>\n<li>Mutations (random building blocks) are added to the population to prevent getting stuck in a local minimum.</li>\n<li>The above steps are repeated on the new population until a finish criterion is met.</li>\n</ol>\n<hr/>\n<h2 id=\"imports\">Imports</h2>\n<p>First, we need to import the libraries we will be using.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">math</span>\n<span class=\"kn\">import</span> <span class=\"nn\">time</span>\n<span class=\"kn\">from</span> <span class=\"nn\">functools</span> <span class=\"kn\">import</span> <span class=\"n\">partial</span>\n<span class=\"kn\">from</span> <span class=\"nn\">itertools</span> <span class=\"kn\">import</span> <span class=\"n\">product</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n<span class=\"kn\">from</span> <span class=\"nn\">typing</span> <span class=\"kn\">import</span> <span class=\"n\">Callable</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">AllChem</span><span class=\"p\">,</span> <span class=\"n\">DataStructs</span><span class=\"p\">,</span> <span class=\"n\">Descriptors</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n</code></pre></div></div>\n<h2 id=\"expensive-scoring-function\">Expensive scoring function</h2>\n<p>The first function we will define is the expensive scoring function, this will take a list of RDKit molecules and return a list of scores. As in the previous example, I'm going to try find the compound from within the library with the lowest calculated Log P (cLogP). This is actually a very fast calculation and can be done exhaustively, which means we can confirm if the genetic algorithm is finding the lowest value and triggering the early stopping.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_logp</span><span class=\"p\">(</span><span class=\"n\">mols</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculate the logP value for a list of compounds.\n\n    Args:\n        mols (list[Chem.Mol]): The molecules.\n\n    Returns:\n        list[float]: The scores of the molecules.\n    \"\"\"</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">mols</span><span class=\"p\">]</span>\n</code></pre></div></div>\n<h2 id=\"defining-some-useful-functions\">Defining some useful functions</h2>\n<p>Next, we need to define some useful functions for the active learning pipeline.</p>\n<p>The first function takes the indices of three building blocks and enumerates the product of their reaction. Since not all combinations are possible, it will return None if the reaction fails. The second function takes the surviving population from the selection pressure and creates a new population by shuffling the building blocks. The third takes the new shuffled population and adds in random mutations, this prevents the algorithm getting stuck in a local minimum by ensuring building block diversity.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_molecule</span><span class=\"p\">(</span>\n    <span class=\"n\">r1</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">r2</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">r3</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]],</span>\n    <span class=\"n\">rxn</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">rdChemReactions</span><span class=\"p\">.</span><span class=\"n\">ChemicalReaction</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"React building blocks to make a molecule.\n\n    Args:\n        r1 (int): Index of the first building block.\n        r2 (int): Index of the second building block.\n        r3 (int): Index of the third building block.\n        building_blocks (list[list[str]]): The building blocks.\n        rxn (Chem.rdChemReactions.ChemicalReaction): The reaction to combine the\n                                                     building blocks.\n\n\n    Returns:\n        Chem.Mol | None: The molecule or None if the reaction fails.\n    \"\"\"</span>\n    <span class=\"n\">bbs</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"n\">r1</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">][</span><span class=\"n\">r2</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">][</span><span class=\"n\">r3</span><span class=\"p\">]]</span>\n    <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">bbs</span><span class=\"p\">]</span>\n    <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n        <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n        <span class=\"k\">return</span> <span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"bp\">None</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">shuffle_population</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">population_size</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Shuffle the population building blocks.\n\n    Args:\n        data (pd.DataFrame): The surviving population data.\n        population_size (int): The size of the population.\n\n    Returns:\n        pd.DataFrame: The shuffled population.\n    \"\"\"</span>\n    <span class=\"n\">r1s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r1\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">r2s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r2\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">r3s</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"r3\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">()</span>\n    <span class=\"n\">new_population</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"r1\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r1s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n            <span class=\"s\">\"r2\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r2s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n            <span class=\"s\">\"r3\"</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">choice</span><span class=\"p\">(</span><span class=\"n\">r3s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">population_size</span><span class=\"p\">)],</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">new_population</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">mutate_population</span><span class=\"p\">(</span>\n    <span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">mutation_rate</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]]</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Mutate the population building blocks.\n\n    Args:\n        data (pd.DataFrame): The surviving population data.\n        mutation_rate (float): The fraction of building blocks to mutate.\n        building_blocks (list[list[str]]): The building blocks.\n\n    Returns:\n        pd.DataFrame: The mutated population.\n    \"\"\"</span>\n    <span class=\"k\">for</span> <span class=\"n\">count</span><span class=\"p\">,</span> <span class=\"n\">column</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">columns</span><span class=\"p\">):</span>\n        <span class=\"n\">selection</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">(</span><span class=\"n\">frac</span><span class=\"o\">=</span><span class=\"n\">mutation_rate</span><span class=\"p\">)</span>\n        <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">loc</span><span class=\"p\">[</span><span class=\"n\">selection</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span>\n            <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"n\">count</span><span class=\"p\">]),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">selection</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">data</span>\n</code></pre></div></div>\n<h2 id=\"genetic-algorithm-pipeline\">Genetic algorithm pipeline</h2>\n<p>The genetic algorithm needs the lists of building blocks, a reaction to couple them to enumerate products, and a function to score the molecules. It also has a number of hyperparameters than can be tuned:</p>\n<ul>\n<li><code class=\"language-plaintext highlighter-rouge\">population_size</code>: The number of molecules in each generation to enumerate. A larger population will have a greater diversity of building blocks but will take longer to enumerate and score.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">num_generations</code>: The number of cycles of the genetic algorithm to run. More generations will give a better chance of finding the optimum value but will take longer to run.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">selection_pressure</code>: The fraction of the population to remove each cycle. A stronger selection pressure will speed up selection but increase the chance of being caught in a local minimum.</li>\n<li><code class=\"language-plaintext highlighter-rouge\">mutation_rate</code>: The proportion of building blocks in the new population to replace with a random building block. A too high value will stop the algorithm finding the best combination, but a too low value will also increase the chance of being caught in a local minimum.</li>\n</ul>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_genetic_algorithm</span><span class=\"p\">(</span>\n    <span class=\"n\">building_blocks</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]],</span>\n    <span class=\"n\">rnx</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">rdChemReactions</span><span class=\"p\">.</span><span class=\"n\">ChemicalReaction</span><span class=\"p\">,</span>\n    <span class=\"n\">population_size</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">num_generations</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">selection_pressure</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span>\n    <span class=\"n\">mutation_rate</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span>\n    <span class=\"n\">scoring_function</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">,</span>\n    <span class=\"n\">minimize</span><span class=\"p\">:</span> <span class=\"nb\">bool</span><span class=\"p\">,</span>\n    <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">|</span> <span class=\"bp\">None</span> <span class=\"o\">=</span> <span class=\"bp\">None</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run the genetic algorithm.\n\n    Args:\n        building_blocks (list[list[str]]): The building blocks.\n        rnx (Chem.rdChemReactions.ChemicalReaction): The reaction to combine the\n                                                     building blocks.\n        population_size (int): The size of the population in each generation.\n        num_generations (int): The number of generations to run.\n        selection_pressure (float): The fraction of the population to discard.\n        mutation_rate (float): The fraction of the population to mutate.\n        scoring_function (Callable): The function to score the molecules.\n        minimize (bool): Whether to minimize the scoring function.\n        early_stopping_value (float | None, optional): The value to stop early at.\n                                                       Defaults to None.\n\n    Returns:\n        pd.DataFrame: The history of the population.\n    \"\"\"</span>\n    <span class=\"c1\"># To keep track of the population each generation\n</span>    <span class=\"n\">history</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">()</span>\n\n    <span class=\"c1\"># Choose initial population\n</span>    <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"s\">\"r1\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n            <span class=\"s\">\"r2\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n            <span class=\"s\">\"r3\"</span><span class=\"p\">:</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">randint</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">building_blocks</span><span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">]),</span> <span class=\"n\">population_size</span><span class=\"p\">),</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"k\">for</span> <span class=\"n\">generation</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_generations</span><span class=\"p\">):</span>\n        <span class=\"c1\"># Generate molecules\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"nb\">apply</span><span class=\"p\">(</span>\n            <span class=\"k\">lambda</span> <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">make_molecule</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r1\"</span><span class=\"p\">],</span> <span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r2\"</span><span class=\"p\">],</span> <span class=\"n\">x</span><span class=\"p\">[</span><span class=\"s\">\"r3\"</span><span class=\"p\">],</span> <span class=\"n\">building_blocks</span><span class=\"p\">,</span> <span class=\"n\">rnx</span><span class=\"p\">),</span>\n            <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Kill off fatal mutants\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">().</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n        <span class=\"c1\"># Score molecules\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">scoring_function</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">].</span><span class=\"n\">to_list</span><span class=\"p\">())</span>\n\n        <span class=\"c1\"># Save population for analysis\n</span>        <span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"generation\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">generation</span>\n        <span class=\"n\">history</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">([</span><span class=\"n\">history</span><span class=\"p\">,</span> <span class=\"n\">population</span><span class=\"p\">])</span>\n\n        <span class=\"c1\"># Early stopping\n</span>        <span class=\"k\">if</span> <span class=\"n\">early_stopping_value</span><span class=\"p\">:</span>\n            <span class=\"k\">if</span> <span class=\"n\">minimize</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"nb\">round</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">].</span><span class=\"nb\">min</span><span class=\"p\">(),</span> <span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"o\">&lt;=</span> <span class=\"nb\">round</span><span class=\"p\">(</span>\n                    <span class=\"n\">early_stopping_value</span><span class=\"p\">,</span> <span class=\"mi\">5</span>\n                <span class=\"p\">):</span>\n                    <span class=\"k\">break</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">if</span> <span class=\"nb\">round</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">[</span><span class=\"s\">\"score\"</span><span class=\"p\">].</span><span class=\"nb\">max</span><span class=\"p\">(),</span> <span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"o\">&gt;=</span> <span class=\"nb\">round</span><span class=\"p\">(</span>\n                    <span class=\"n\">early_stopping_value</span><span class=\"p\">,</span> <span class=\"mi\">5</span>\n                <span class=\"p\">):</span>\n                    <span class=\"k\">break</span>\n\n        <span class=\"c1\"># Select top performing molecules\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n            <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"score\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"n\">minimize</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">population_size</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\">-</span> <span class=\"n\">selection_pressure</span><span class=\"p\">)))</span>\n            <span class=\"p\">.</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n\n        <span class=\"c1\"># Shuffle and mutate\n</span>        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">shuffle_population</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">,</span> <span class=\"n\">population_size</span><span class=\"p\">)</span>\n        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">mutate_population</span><span class=\"p\">(</span><span class=\"n\">population</span><span class=\"p\">,</span> <span class=\"n\">mutation_rate</span><span class=\"p\">,</span> <span class=\"n\">building_blocks</span><span class=\"p\">)</span>\n        <span class=\"n\">population</span> <span class=\"o\">=</span> <span class=\"n\">population</span><span class=\"p\">.</span><span class=\"n\">drop_duplicates</span><span class=\"p\">().</span><span class=\"n\">reset_index</span><span class=\"p\">(</span><span class=\"n\">drop</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">history</span>\n</code></pre></div></div>\n<p>This function fully enumerates a virtual library by combining three sets of building blocks. The smi files used here were borrowed from <a href=\"https://github.com/PatWalters/TS\">Pat Walters repository on Thompson sampling</a>. This is not necessary for the genetic algorithm but will allow us to see if the genetic algorithm if finding the best values.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">build_virtual_library</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Build a virtual library by coupling building blocks from the input smi files.\n\n    Returns:\n        pd.DataFrame: The virtual library.\n    \"\"\"</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index_col</span><span class=\"o\">=</span><span class=\"s\">\"smiles\"</span><span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">index</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n    <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n        <span class=\"n\">reaction_smarts</span> <span class=\"o\">=</span> <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n        <span class=\"n\">bb_types</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]</span>\n        <span class=\"n\">rxn</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span><span class=\"n\">reaction_smarts</span><span class=\"p\">)</span>\n\n        <span class=\"n\">building_blocks</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"n\">bb_types</span><span class=\"p\">:</span>\n            <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n                    <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n                    <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n            <span class=\"n\">building_blocks</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n\n        <span class=\"n\">total_prods</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"p\">.</span><span class=\"n\">prod</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">building_blocks</span><span class=\"p\">])</span>\n\n        <span class=\"n\">product_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n        <span class=\"k\">for</span> <span class=\"n\">reagents</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">product</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">building_blocks</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">total_prods</span><span class=\"p\">):</span>\n            <span class=\"n\">reagent_mol_list</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">reagents</span><span class=\"p\">]</span>\n            <span class=\"n\">products</span> <span class=\"o\">=</span> <span class=\"n\">rxn</span><span class=\"p\">.</span><span class=\"n\">RunReactants</span><span class=\"p\">(</span><span class=\"n\">reagent_mol_list</span><span class=\"p\">)</span>\n            <span class=\"k\">if</span> <span class=\"n\">products</span><span class=\"p\">:</span>\n                <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">SanitizeMol</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n                <span class=\"n\">product_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">products</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n\n        <span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n            <span class=\"n\">product_list</span><span class=\"p\">,</span>\n            <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">m</span> <span class=\"ow\">in</span> <span class=\"n\">product_list</span><span class=\"p\">],</span>\n            <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"mol\"</span><span class=\"p\">],</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/library.csv\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">library</span>\n\n\n<span class=\"c1\"># Chemistry parameters\n</span><span class=\"n\">RXN</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">ReactionFromSmarts</span><span class=\"p\">(</span>\n    <span class=\"s\">\"N[c:4][c:3]C(O)=O.[#6:1][NH2].[#6:2]C(=O)[OH]&gt;&gt;[C:2]c1n[c:4][c:3]c(=O)n1[C:1]\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">BUILDING_BLOCKS</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">bb</span> <span class=\"ow\">in</span> <span class=\"p\">[</span><span class=\"s\">\"aminobenzoic\"</span><span class=\"p\">,</span> <span class=\"s\">\"carboxylic_acids\"</span><span class=\"p\">,</span> <span class=\"s\">\"primary_amines\"</span><span class=\"p\">]:</span>\n    <span class=\"n\">smil</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n    <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">bb</span><span class=\"si\">}</span><span class=\"s\">_100.smi\"</span><span class=\"p\">),</span> <span class=\"s\">\"r\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n        <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">f</span><span class=\"p\">.</span><span class=\"n\">readlines</span><span class=\"p\">():</span>\n            <span class=\"n\">smiles</span><span class=\"p\">,</span> <span class=\"n\">_</span> <span class=\"o\">=</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n            <span class=\"n\">smil</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">smil</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-1-finding-the-compound-with-the-lowest-clogp\">Example 1: Finding the compound with the lowest cLogP</h2>\n<p>The genetic algorithm was used to find the lowest cLogP from within the library. This was repeated 10 times with no tuning of the hyperparameters which may improve performance. The genetic algorithm reliably finds the combination of building blocks with the lowest cLogP in the combinatorial library, nearly 100 times faster than enumerating the entire library. On average the genetic algorithm only had to enumerate and score 600 combinations before it found the best scoring combination.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># GA parameters\n</span><span class=\"n\">POPULATION_SIZE</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">NUM_GENERATIONS</span> <span class=\"o\">=</span> <span class=\"mi\">20</span>\n<span class=\"n\">SELECTION_PRESSURE</span> <span class=\"o\">=</span> <span class=\"mf\">0.5</span>\n<span class=\"n\">MUTATION_RATE</span> <span class=\"o\">=</span> <span class=\"mf\">0.1</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">True</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Create the virtual library\n</span><span class=\"n\">virtual_library_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">library</span> <span class=\"o\">=</span> <span class=\"n\">build_virtual_library</span><span class=\"p\">()</span>\n<span class=\"n\">virtual_library_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Virtual library created in </span><span class=\"si\">{</span><span class=\"n\">virtual_library_end</span> <span class=\"o\">-</span> <span class=\"n\">virtual_library_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Find the minimum logP in the library\n</span><span class=\"n\">logp_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Calculating logP values for the library\"</span><span class=\"p\">)</span>\n<span class=\"n\">all_clogp_values</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">Descriptors</span><span class=\"p\">.</span><span class=\"n\">MolLogP</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">())]</span>\n<span class=\"n\">logp_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Minimum logP in the library: </span><span class=\"si\">{</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"LogP calculations took </span><span class=\"si\">{</span><span class=\"n\">logp_end</span> <span class=\"o\">-</span> <span class=\"n\">logp_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Run the genetic algorithm\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Running genetic algorithm </span><span class=\"si\">{</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"si\">}</span><span class=\"s\"> times\"</span><span class=\"p\">)</span>\n<span class=\"n\">ga_times</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">max_generations</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"n\">ga_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">logp_ga_run</span> <span class=\"o\">=</span> <span class=\"n\">run_genetic_algorithm</span><span class=\"p\">(</span>\n        <span class=\"n\">building_blocks</span><span class=\"o\">=</span><span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">,</span>\n        <span class=\"n\">rnx</span><span class=\"o\">=</span><span class=\"n\">RXN</span><span class=\"p\">,</span>\n        <span class=\"n\">population_size</span><span class=\"o\">=</span><span class=\"n\">POPULATION_SIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">num_generations</span><span class=\"o\">=</span><span class=\"n\">NUM_GENERATIONS</span><span class=\"p\">,</span>\n        <span class=\"n\">selection_pressure</span><span class=\"o\">=</span><span class=\"n\">SELECTION_PRESSURE</span><span class=\"p\">,</span>\n        <span class=\"n\">mutation_rate</span><span class=\"o\">=</span><span class=\"n\">MUTATION_RATE</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">calc_logp</span><span class=\"p\">,</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">all_clogp_values</span><span class=\"p\">),</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ga_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">ga_times</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span><span class=\"p\">)</span>\n    <span class=\"n\">max_generations</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">logp_ga_run</span><span class=\"p\">.</span><span class=\"n\">generation</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Genetic algorithm run took </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds on average, with a maximum of </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">max_generations</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> generations\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Calculate improvement\n</span><span class=\"n\">ga_time</span> <span class=\"o\">=</span> <span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span>\n<span class=\"n\">virtual_library_time</span> <span class=\"o\">=</span> <span class=\"n\">virtual_library_end</span> <span class=\"o\">-</span> <span class=\"n\">virtual_library_start</span>\n<span class=\"n\">logp_time</span> <span class=\"o\">=</span> <span class=\"n\">logp_end</span> <span class=\"o\">-</span> <span class=\"n\">logp_start</span>\n<span class=\"n\">improvement</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">virtual_library_time</span> <span class=\"o\">+</span> <span class=\"n\">logp_time</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"n\">ga_time</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"GA was </span><span class=\"si\">{</span><span class=\"n\">improvement</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> times faster than building and scoring the virtual library\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Creating virtual library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1000000/1000000 [01:48&lt;00:00, 9183.87it/s]\nVirtual library created in 120.60 seconds\n\nCalculating logP values for the library\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 132500/132500 [00:27&lt;00:00, 4803.96it/s]\nMinimum logP in the library: -5.00\nLogP calculations took 27.59 seconds\n\nRunning genetic algorithm 10 times\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:14&lt;00:00,  1.41s/it]\n\nGenetic algorithm run took 1.41 seconds on average, with a maximum of 12 generations\nGA was 81.90 times faster than building and scoring the virtual library\n</code></pre></div></div>\n<hr/>\n<h2 id=\"example-2-recovering-a-random-compound-from-within-the-library-using-tanimoto-similarity\">Example 2: Recovering a random compound from within the library using Tanimoto similarity</h2>\n<p>A reference molecule was randomly selected from the library, and the genetic algorithm was used to recover it from the library by maximising the Tanimoto similarity. In the active learning experiments, this was failing, possibly due to the low number of compounds from within the library with high Tanimoto similarities. The genetic algorithm was able to recover the reference molecule every time, taking less than a second to do so each time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calc_similarity</span><span class=\"p\">(</span><span class=\"n\">comparison_mols</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">],</span> <span class=\"n\">ref_mol</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">Mol</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">float</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Calculate the Tanimoto similarity to a reference compound.\n\n    Args:\n        comparison_mols (list[Chem.Mol]): List of molecules to compare.\n        ref_mol (Chem.Mol): The reference molecule.\n\n    Returns:\n        list[float]: List of similarity scores\n    \"\"\"</span>\n    <span class=\"n\">fpgen</span> <span class=\"o\">=</span> <span class=\"n\">AllChem</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">()</span>\n    <span class=\"n\">ref_fp</span> <span class=\"o\">=</span> <span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">ref_mol</span><span class=\"p\">)</span>\n    <span class=\"n\">comparison_fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">fpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_mols</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"p\">[</span><span class=\"n\">DataStructs</span><span class=\"p\">.</span><span class=\"n\">FingerprintSimilarity</span><span class=\"p\">(</span><span class=\"n\">ref_fp</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">x</span> <span class=\"ow\">in</span> <span class=\"n\">comparison_fps</span><span class=\"p\">]</span>\n\n\n<span class=\"c1\"># Score the virtual library\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Scoring the virtual library\"</span><span class=\"p\">)</span>\n<span class=\"n\">tanimoto_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"n\">all_tanimoto_values</span> <span class=\"o\">=</span> <span class=\"n\">calc_similarity</span><span class=\"p\">(</span>\n    <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">to_list</span><span class=\"p\">(),</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">().</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n<span class=\"p\">)</span>\n<span class=\"n\">tanimoto_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Tanimoto calculations took </span><span class=\"si\">{</span><span class=\"n\">tanimoto_end</span> <span class=\"o\">-</span> <span class=\"n\">tanimoto_start</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds\"</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># GA parameters\n</span><span class=\"n\">POPULATION_SIZE</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">NUM_GENERATIONS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">SELECTION_PRESSURE</span> <span class=\"o\">=</span> <span class=\"mf\">0.5</span>\n<span class=\"n\">MUTATION_RATE</span> <span class=\"o\">=</span> <span class=\"mf\">0.1</span>\n<span class=\"n\">MINIMIZE</span> <span class=\"o\">=</span> <span class=\"bp\">False</span>\n<span class=\"n\">NUMBER_OF_REPEATS</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n\n<span class=\"c1\"># Run the genetic algorithm\n</span><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Running genetic algorithm </span><span class=\"si\">{</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"si\">}</span><span class=\"s\"> times\"</span><span class=\"p\">)</span>\n<span class=\"n\">ga_times</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"n\">max_generations</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n<span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">NUMBER_OF_REPEATS</span><span class=\"p\">)):</span>\n    <span class=\"c1\"># Choose random reference molecule\n</span>    <span class=\"n\">ref_mol</span> <span class=\"o\">=</span> <span class=\"n\">library</span><span class=\"p\">.</span><span class=\"n\">mol</span><span class=\"p\">.</span><span class=\"n\">sample</span><span class=\"p\">().</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n\n    <span class=\"c1\"># Run the genetic algorithm\n</span>    <span class=\"n\">ga_start</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">tanimoto_ga_run</span> <span class=\"o\">=</span> <span class=\"n\">run_genetic_algorithm</span><span class=\"p\">(</span>\n        <span class=\"n\">building_blocks</span><span class=\"o\">=</span><span class=\"n\">BUILDING_BLOCKS</span><span class=\"p\">,</span>\n        <span class=\"n\">rnx</span><span class=\"o\">=</span><span class=\"n\">RXN</span><span class=\"p\">,</span>\n        <span class=\"n\">population_size</span><span class=\"o\">=</span><span class=\"n\">POPULATION_SIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">num_generations</span><span class=\"o\">=</span><span class=\"n\">NUM_GENERATIONS</span><span class=\"p\">,</span>\n        <span class=\"n\">selection_pressure</span><span class=\"o\">=</span><span class=\"n\">SELECTION_PRESSURE</span><span class=\"p\">,</span>\n        <span class=\"n\">mutation_rate</span><span class=\"o\">=</span><span class=\"n\">MUTATION_RATE</span><span class=\"p\">,</span>\n        <span class=\"n\">scoring_function</span><span class=\"o\">=</span><span class=\"n\">partial</span><span class=\"p\">(</span><span class=\"n\">calc_similarity</span><span class=\"p\">,</span> <span class=\"n\">ref_mol</span><span class=\"o\">=</span><span class=\"n\">ref_mol</span><span class=\"p\">),</span>\n        <span class=\"n\">minimize</span><span class=\"o\">=</span><span class=\"n\">MINIMIZE</span><span class=\"p\">,</span>\n        <span class=\"n\">early_stopping_value</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ga_end</span> <span class=\"o\">=</span> <span class=\"n\">time</span><span class=\"p\">.</span><span class=\"n\">time</span><span class=\"p\">()</span>\n    <span class=\"n\">ga_times</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">ga_end</span> <span class=\"o\">-</span> <span class=\"n\">ga_start</span><span class=\"p\">)</span>\n    <span class=\"n\">max_generations</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">tanimoto_ga_run</span><span class=\"p\">.</span><span class=\"n\">generation</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">())</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"Genetic algorithm run took </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> seconds on average, with a maximum of </span><span class=\"si\">{</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">max_generations</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> generations\"</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Calculate improvement\n</span><span class=\"n\">tanimoto_time</span> <span class=\"o\">=</span> <span class=\"n\">tanimoto_end</span> <span class=\"o\">-</span> <span class=\"n\">tanimoto_start</span>\n<span class=\"n\">improvement</span> <span class=\"o\">=</span> <span class=\"n\">tanimoto_time</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">average</span><span class=\"p\">(</span><span class=\"n\">ga_times</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span>\n    <span class=\"sa\">f</span><span class=\"s\">\"GA was </span><span class=\"si\">{</span><span class=\"n\">improvement</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> times faster than scoring the virtual library with Tanimoto similarity\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Scoring the virtual library\nTanimoto calculations took 7.41 seconds\nRunning genetic algorithm 10 times\n100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 10/10 [00:07&lt;00:00,  1.29it/s]\n\nGenetic algorithm run took 0.77 seconds on average, with a maximum of 9 generations\nGA was 9.60 times faster than scoring the virtual library with Tanimoto similarity\n</code></pre></div></div>","doi":"https://doi.org/10.59350/7tack-xqe19","guid":"https://jonswain.github.io/ultra-large-libraries-part-2","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1735776000,"rid":"t0c15-jak63","summary":"This is part 2 of a a planned three post series on working with large chemical libraries. The notebook used to create this post, and all the files can be found in this github repo. Combinatorial libraries Combinatorial libraries can grow quickly, combining three sets of 1,000 building blocks allows you to access one billion possibilities.","tags":["Ai","Cheminformatics","Data-science","Genetic-algorithms","Ultra-large-libraries"],"title":"Working with Large Virtual Chemical Libraries: Part 2 - Genetic Algorithms","updated_at":1788763530,"url":"https://jonswain.github.io/ultra-large-libraries-part-2/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>Deep Learning models have traditionally performed well on unstructured data such as text and images, but poorly on structured tabular data, and are usually outperformed by Gradient Boosted Decision Trees (GBDTs) on tabular chemical data. TabPFN (Tabular Prior-data Fitted Network) is a transformer-based foundation model for tabular data, pre-trained on millions of synthetic datasets to solve supervised learning tasks, with state-of-the-art performance on benchmarks. But does it work for cheminformatics?</p>\n<hr/>\n<h2 id=\"tabpfn\">TabPFN</h2>\n<p>A recent paper published in Nature, <a href=\"https://doi.org/10.1038/s41586-024-08328-6\">Accurate predictions on small data with a tabular foundation model by Hollmann et al.</a>, describes a new Deep Learning (DL) algorithm for making predictions on tabular data, TabPFN (Tabular Prior-data Fitted Network). TabPFN is a transformer-based foundation model for tabular data that operates via in-context learning (ICL), enabling it to train and predict on an entire dataset in a single forward pass.</p>\n<p>The authors generated over 100 million synthetic datasets using structural causal models (SCMs). These models simulate causal relationships and mimic real-world tabular data challenges, such as non-linear relationships, missing values, outliers, and diverse feature types (e.g., categorical, ordinal, numerical). These datasets sampled high-level hyperparameters (such as dataset size and number of features), both classification and regression tasks, and Gaussian noise was added to mimic real-world complexities.</p>\n<p>These synthetic datasets were used to pre-train a transformer model. During the training, parts of the datasets were masked, and the model is trained to predict masked target values in synthetic datasets, given features and labelled samples as context. The parameters of the neural network were updated until the predictions matched the masked values. Through this training, the model learns to fill in missing data from a dataset. The model learns a generic prediction algorithm that approximates Bayesian inference for the synthetic data prior, enabling robust handling of unseen datasets.</p>\n<p>When making predictions TabPFN uses ICL and processes an entire dataset (both labelled and test samples) in one pass, performing training and inference simultaneously.</p>\n<h2 id=\"therapeutic-data-commons-tdc\">Therapeutic data commons (TDC)</h2>\n<p>The <a href=\"https://tdcommons.ai/\">Therapeutic data commons (TDC)</a> aims to help development of AI/ML tools for therapeutic science by providing datasets and curated benchmarks to assess the performance of new methods. The TDC ADMET benchmark group contains 22 datasets for molecular property prediction, ranging from 475 to 13,130 entries, with both classification and regression tasks. The datasets contain SMILES strings for each chemical compound, and a target variable to be predicted. These can all be easily downloaded using their Python library.</p>\n<h2 id=\"using-tabpfn-on-tdc-datasets\">Using TabPFN on TDC datasets</h2>\n<p>TabPFN is designed for small tabular datasets, and the default parameters limit to training on 10,000 entries with 500 features. If a dataset had more than 10,000 entries in the training and validation sets, a random selection of 10,000 entries was used. For each dataset, the training and validation data were combined, and the test set put aside. For all entries, the 210 RDKit descriptors were calculated and used as the features for training. Predictions were made on the test set and compared to the true values to evaluate the performance of TabPFN. The training was repeated using molecular fingerprints (MACCS keys and ECPF folded to 500 bits), but the performance was found to be lower than using RDKit calculated properties.</p>\n<p><strong>NOTE</strong>: Due to memory limits on my computer, I had to limit to datasets with fewer than 1,800 entries in the training data. Training models for the larger datasets is ongoing.</p>\n<h2 id=\"performance-on-tdc-datasets\">Performance on TDC datasets</h2>\n<p>Using the RDKit calculated descriptors as features, TabPFN come in the top 10 models for all TDC datasets apart from \"Clearance_Hepatocyte_Az\". It comes 3rd for the \"Vdss_Lombardo\" dataset, 2nd for \"Caco2_Wang\", \"Pgp_Broccatelli\", and \"Bbb_Martins\" datasets, and is the highest performing model for the \"Clearance_Microsome_Az\" dataset. The high performing datasets include both classification and regression tasks and doesn't seem to be a clear link between classification performance and imbalanced datasets. \"Cyp2C9_Substrate_Carbonmangels\" is somewhat imbalanced (19.3% positive - TDC rank: 10th) but so are \"HIA_Hou\" (11.1% negative - TDC rank: 5th), and \"Bioavailability_Ma\", (22.9% negative - TDC rank: 5th). The strongest link seems to be between dataset size and performance, with all the highest performing models having &gt;900 entries.</p>\n<p>The training time is an average of five repeats, using WSL on a Windows machine with 8 GB RAM allocated to WSL.</p>\n<table>\n<thead>\n<tr>\n<th>Dataset</th>\n<th>Size</th>\n<th>Task</th>\n<th>Metric</th>\n<th>Training time (min)</th>\n<th>TabFPN performance</th>\n<th>Current TDC best performance</th>\n<th>TabPFN TDC leaderboard rank</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Caco2_Wang</td>\n<td>906</td>\n<td>Regression</td>\n<td>MAE</td>\n<td>8.25</td>\n<td>0.282 \u00b1 0.005</td>\n<td>0.276 \u00b1 0.005</td>\n<td>2nd</td>\n</tr>\n<tr>\n<td>HIA_Hou</td>\n<td>578</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.17</td>\n<td>0.987 \u00b1 0.001</td>\n<td>0.990 \u00b1 0.002</td>\n<td>5th</td>\n</tr>\n<tr>\n<td>Pgp_Broccatelli</td>\n<td>1218</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>12.77</td>\n<td>0.936 \u00b1 0.004</td>\n<td>0.938 \u00b1 0.002</td>\n<td>2th</td>\n</tr>\n<tr>\n<td>Bioavailability_Ma</td>\n<td>640</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>4.32</td>\n<td>0.735 \u00b1 0.016</td>\n<td>0.753 \u00b1 0.000</td>\n<td>5th</td>\n</tr>\n<tr>\n<td>Bbb_Martins</td>\n<td>2030</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>35.51</td>\n<td>0.917 \u00b1 0.003</td>\n<td>0.920 \u00b1 0.006</td>\n<td>2nd</td>\n</tr>\n<tr>\n<td>Vdss_Lombardo</td>\n<td>1130</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>12.91</td>\n<td>0.693 \u00b1 0.004</td>\n<td>0.713 \u00b1 0.007</td>\n<td>3rd</td>\n</tr>\n<tr>\n<td>Cyp2D6_Substrate_Carbonmangels</td>\n<td>667</td>\n<td>Classification</td>\n<td>AUPRC</td>\n<td>4.82</td>\n<td>0.714 \u00b1 0.009</td>\n<td>0.736</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Cyp3A4_Substrate_Carbonmangels</td>\n<td>670</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.97</td>\n<td>0.641 \u00b1 0.004</td>\n<td>0.667 \u00b1 0.019</td>\n<td>7th</td>\n</tr>\n<tr>\n<td>Cyp2C9_Substrate_Carbonmangels</td>\n<td>669</td>\n<td>Classification</td>\n<td>AUPRC</td>\n<td>4.28</td>\n<td>0.400 \u00b1 0.013</td>\n<td>0.441 \u00b1 0.033</td>\n<td>10th</td>\n</tr>\n<tr>\n<td>Half_Life_Obach</td>\n<td>667</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>4.20</td>\n<td>0.546 \u00b1 0.013</td>\n<td>0.576 \u00b1 0.025</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Clearance_Microsome_Az</td>\n<td>1102</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>12.71</td>\n<td>0.632 \u00b1 0.006</td>\n<td>0.630 \u00b1 0.010</td>\n<td>1st</td>\n</tr>\n<tr>\n<td>Clearance_Hepatocyte_Az</td>\n<td>1213</td>\n<td>Regression</td>\n<td>Spearman</td>\n<td>11.27</td>\n<td>0.391 \u00b1 0.004</td>\n<td>0.536 \u00b1 0.02</td>\n<td>&gt;10th</td>\n</tr>\n<tr>\n<td>Herg</td>\n<td>655</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>3.54</td>\n<td>0.850 \u00b1 0.002</td>\n<td>0.880 \u00b1 0.002</td>\n<td>6th</td>\n</tr>\n<tr>\n<td>Dili</td>\n<td>475</td>\n<td>Classification</td>\n<td>AUROC</td>\n<td>1.92</td>\n<td>0.910 \u00b1 0.005</td>\n<td>0.925 \u00b1 0.005</td>\n<td>6th</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"advantages\">Advantages</h2>\n<p>On some of the TDC datasets, TabPFN exhibits state-of-the-art performance out of the box. Regularly outperforming other DL methods such as graph neural networks (GNNs) designed specifically for cheminformatics. It can learn complex relationships between features and the target on small datasets due to the pre-training on synthetic data. TabPFN uses a scikit-learn style API, making it very easy to use and integrate into existing workflows. Since TabPFN approximates Bayesian inference, it can include important features such as uncertainty estimation possible.</p>\n<h2 id=\"limitations\">Limitations</h2>\n<p>TabPFN is limited to 10,000 entries with 500 features as a default, as this is the limit of the synthetic data it was trained on. It is possible to use datasets larger than this using the <code class=\"language-plaintext highlighter-rouge\">ignore_pretraining_limits=True</code> parameter, but this may lead to very long training times. The computational requirements for TabPFN scale quadratically with the number of samples (n) and the number of features (m), i.e. O(n<sup>2</sup> + m<sup>2</sup>) so training and predicting on larger datasets gets increasing longer. The training time can be long, though not significantly worse than other DL methods such as Chemprop in my experience. The single pass training and prediction architecture is not great for situations when you need to make multiple predictions, but the authors have included an option to cache the effect of the training data (using the <code class=\"language-plaintext highlighter-rouge\">fit_mode=\"fit_with_cache\"</code> parameter) when training the model, making the training and prediction API much more like a standard scikit-learn ML model.</p>\n<h2 id=\"conclusions\">Conclusions</h2>\n<p>TabPFN is definitely a method to consider when building QSAR models, especially when you have a small dataset. With more time (and computing power), I'd be interested to see if combining the RDKit calculated descriptors with a molecular fingerprint (i.e. adding MACCS keys would make 376 feature columns) improves performance. In my experience sometimes combining the local description of molecules using molecular fingerprints with global descriptions of molecules using calculated descriptors creates a very good embedding of your molecules.</p>\n<p>It's possible to fine tune TabPFN with specialised datasets. Perhaps fine tuning with a large number of chemical datasets, or synthetic datasets of calculated molecular properties would create a chemical tabular foundation model (ChemTabPFN?), capable of accurately predicting chemical property-structure relationships.</p>\n<h2 id=\"references\">References</h2>\n<ul>\n<li>The code used can be found in <a href=\"https://github.com/jonswain/tabpfn-tdc\">this Github repo</a></li>\n<li><a href=\"https://github.com/PriorLabs/tabpfn\">TabPFN Github repo</a></li>\n</ul>","doi":"https://doi.org/10.59350/y9a60-68b25","guid":"https://jonswain.github.io/TabPFN-for-chemical-datasets","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1737504000,"reference":[{"id":"https://github.com/jonswain/tabpfn-tdc","unstructured":"The code used can be found in this Github repo"},{"id":"https://github.com/PriorLabs/tabpfn","unstructured":"TabPFN Github repo"}],"rid":"dcv48-dvh59","summary":"Deep Learning models have traditionally performed well on unstructured data such as text and images, but poorly on structured tabular data, and are usually outperformed by Gradient Boosted Decision Trees (GBDTs) on tabular chemical data.","tags":["Ai","Cheminformatics","Data-science","Machine-learning"],"title":"TabPFN for Chemical Datasets","updated_at":1788763526,"url":"https://jonswain.github.io/tabpfn-for-chemical-datasets/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p><img alt=\"An example notification\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/traffic_widget/traffic_notification.png\"/></p>\n<hr/>\n<p>No data science or cheminformatics today!</p>\n<p>I usually cycle or get public transport to work, but occasionally I do have to drive. The traffic in T\u0101maki Makaurau (Auckland) is very variable, especially on rainy or windy days, so I often find myself checking Google Maps every 5 minutes after 4 pm to work out when I need to leave to get home. As with anything that I have to do repeatedly, <a href=\"https://xkcd.com/1205/\">I decided  to automate it</a>.</p>\n<p>My work computer runs Windows so I use <a href=\"https://learn.microsoft.com/en-us/windows/wsl/about\">WSL</a>, which adds a few extra complications. All the code needed can be found in <a href=\"https://github.com/jonswain/traffic-widget\">this repository</a>.</p>\n<p>(I've also been experimenting with Google Gemini as a coding assistant. This wasn't completely <a href=\"https://en.wikipedia.org/wiki/Vibe_coding\">vibe-coding</a>, but as this was a quick personal project, I was much less vigorous in checking the code it generated compared to more important work!)</p>\n<p>All the Python code is kept in a file called <code class=\"language-plaintext highlighter-rouge\">traffic-widget.py</code> which is stored on my WSL disk, I first needed to import the necessary libraries:</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">datetime</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n<span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">from</span> <span class=\"nn\">dotenv</span> <span class=\"kn\">import</span> <span class=\"n\">load_dotenv</span>\n</code></pre></div></div>\n<h2 id=\"getting-the-traffic-data\">Getting the traffic data</h2>\n<p>Google maps doesn't seem to have a <a href=\"https://mapsplatform.google.com/pricing/\">completely free API</a>. They do offer some free usage, but you still have to sign up and give credit card details, which is always a worry in case you accidentally go over the free limits (or accidentally leak your API key to the internet). TomTom on the other hand does have a <a href=\"https://www.tomtom.com/products/map-display-api/\">free API</a>, you have to sign up and get an API key, and it comes with plenty of free requests. After signing up and getting an API key, I first created a TomTomAPI Python class, this uses the API key to make calls to the route calculation endpoint, providing the GPS coordinates for the start and end of your route, and returns the travel time.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">TomTomAPI</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Encapsulates interactions with the TomTom Routing API.\"\"\"</span>\n\n    <span class=\"n\">BASE_URL</span> <span class=\"o\">=</span> <span class=\"s\">\"https://api.tomtom.com/routing/1/calculateRoute\"</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">api_key</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Initialize the TomTomAPI with the API key.\"\"\"</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">api_key</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">get_travel_time</span><span class=\"p\">(</span>\n        <span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">start_lat</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">start_lon</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">end_lat</span><span class=\"p\">:</span> <span class=\"nb\">float</span><span class=\"p\">,</span> <span class=\"n\">end_lon</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n    <span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">int</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"s\">\"\"\"Calculate the travel time between two points.\n\n        Args:\n            start_lat (float): Latitude of the starting point.\n            start_lon (float): Longitude of the starting point.\n            end_lat (float): Latitude of the destination point.\n            end_lon (float): Longitude of the destination point.\n\n        Returns:\n            int: The travel time in seconds, or None if an error occurs.\n        \"\"\"</span>\n        <span class=\"n\">start_point</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">start_lat</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">start_lon</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n        <span class=\"n\">end_point</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">end_lat</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">end_lon</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n        <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">BASE_URL</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">start_point</span><span class=\"si\">}</span><span class=\"s\">:</span><span class=\"si\">{</span><span class=\"n\">end_point</span><span class=\"si\">}</span><span class=\"s\">/json?key=</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">api_key</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n            <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">raise_for_status</span><span class=\"p\">()</span>\n            <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n            <span class=\"n\">travel_time_seconds</span> <span class=\"o\">=</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">\"routes\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"summary\"</span><span class=\"p\">][</span><span class=\"s\">\"travelTimeInSeconds\"</span><span class=\"p\">]</span>\n            <span class=\"k\">return</span> <span class=\"n\">travel_time_seconds</span>\n\n        <span class=\"k\">except</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">exceptions</span><span class=\"p\">.</span><span class=\"n\">RequestException</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"TomTom API Error: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"bp\">None</span>\n        <span class=\"k\">except</span> <span class=\"p\">(</span><span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">JSONDecodeError</span><span class=\"p\">,</span> <span class=\"nb\">KeyError</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"TomTom API Error: Invalid response format or missing data: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"bp\">None</span>\n</code></pre></div></div>\n<h3 id=\"create-the-powershell-script-for-the-notification\">Create the PowerShell script for the notification</h3>\n<p>To display the notifications I used BurntToast, a Windows PowerShell module for displaying Toast Notifications. To install, I needed to run PowerShell as admin and enter:</p>\n<pre><code class=\"language-PowerShell\">Install-Module -Name BurntToast\n</code></pre>\n<p>BurntToast can be called from a PowerShell file (<code class=\"language-plaintext highlighter-rouge\">.ps1</code>). The PowerShell file to display notifications is called <code class=\"language-plaintext highlighter-rouge\">show_notification.ps1</code> and is stored on my Windows disk. The <code class=\"language-plaintext highlighter-rouge\">-Sound Alarm5</code> adds a sound to the notification and makes it last longer and <code class=\"language-plaintext highlighter-rouge\">-AppLogo</code> gives an icon beside the notification.</p>\n<pre><code class=\"language-ps1\">param(\n    [string]$Title,\n    [string]$Message\n)\n\n$ImagePath = 'C:\\Path\\to\\icon.jpg'\n\nNew-BurntToastNotification -Text $Title, $Message -AppLogo $ImagePath -Sound Alarm5\n</code></pre>\n<p>To allow the script to run, it may be necessary to run this command in PowerShell as admin:</p>\n<pre><code class=\"language-PowerShell\">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n</code></pre>\n<h2 id=\"running-the-powershell-script-from-wsl\">Running the PowerShell script from WSL</h2>\n<p>Back in the Python file (<code class=\"language-plaintext highlighter-rouge\">traffic-widget.py</code>), I next needed a class to run the PowerShell file to display the notification.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">WindowsNotifier</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Handles displaying Windows toast notifications.\"\"\"</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">powershell_path</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">powershell_script</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Initialize the WindowsNotifier with the paths to PowerShell.\"\"\"</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_path</span> <span class=\"o\">=</span> <span class=\"n\">powershell_path</span>\n        <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_script</span> <span class=\"o\">=</span> <span class=\"n\">powershell_script</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">show_notification</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">title</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n        <span class=\"s\">\"\"\"Show a Windows toast notification.\n\n        Args:\n            title (str): The title of the notification.\n            message (str): The body of the notification.\n        \"\"\"</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n                <span class=\"p\">[</span>\n                    <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_path</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-ExecutionPolicy\"</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"Bypass\"</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-File\"</span><span class=\"p\">,</span>\n                    <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">powershell_script</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-Title\"</span><span class=\"p\">,</span>\n                    <span class=\"n\">title</span><span class=\"p\">,</span>\n                    <span class=\"s\">\"-Message\"</span><span class=\"p\">,</span>\n                    <span class=\"n\">message</span><span class=\"p\">,</span>\n                <span class=\"p\">],</span>\n                <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n                <span class=\"n\">capture_output</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n                <span class=\"n\">text</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n        <span class=\"k\">except</span> <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">CalledProcessError</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error showing notification: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"PowerShell Output:</span><span class=\"se\">\\n</span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"p\">.</span><span class=\"n\">stderr</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n        <span class=\"k\">except</span> <span class=\"nb\">FileNotFoundError</span><span class=\"p\">:</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error: PowerShell executable or script not found. Check the paths.\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>This function formats the data from the TomTomAPI to be clearer.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">format_travel_time</span><span class=\"p\">(</span><span class=\"n\">label</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">travel_time</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Format the travel time into a readable string.\n\n    Args:\n        label (str): The label for the travel time (e.g., \"Home\").\n        travel_time (int | None): The travel time in seconds, or None if an error occurred.\n\n    Returns:\n        str: The formatted travel time string.\n    \"\"\"</span>\n    <span class=\"n\">now</span> <span class=\"o\">=</span> <span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n    <span class=\"n\">arrival_time</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">now</span> <span class=\"o\">+</span> <span class=\"n\">datetime</span><span class=\"p\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">seconds</span><span class=\"o\">=</span><span class=\"n\">travel_time</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">travel_time</span> <span class=\"k\">else</span> <span class=\"bp\">None</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">travel_time</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">label</span><span class=\"si\">}</span><span class=\"s\">: </span><span class=\"si\">{</span><span class=\"n\">travel_time</span> <span class=\"o\">/</span> <span class=\"mi\">60</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">1</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\"> minutes (arrive at </span><span class=\"si\">{</span><span class=\"n\">arrival_time</span><span class=\"p\">.</span><span class=\"n\">strftime</span><span class=\"p\">(</span><span class=\"s\">'%H</span><span class=\"si\">:</span><span class=\"o\">%</span><span class=\"n\">M</span><span class=\"s\">')</span><span class=\"si\">}</span><span class=\"s\">)\"</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">label</span><span class=\"si\">}</span><span class=\"s\">: ERROR minutes.\"</span>\n</code></pre></div></div>\n<h2 id=\"storing-environment-variables\">Storing environment variables</h2>\n<p>To prevent sharing sensitive data such as my API key and home address, I stored these in a <code class=\"language-plaintext highlighter-rouge\">.env</code> file. This also stores the paths to Windows PowerShell and the PowerShell script. Since this is working between two operating systems, the paths are slightly more complicated than usual. The PowerShell path is the path to the <code class=\"language-plaintext highlighter-rouge\">powershell.exe</code> file on your Windows disk, from your Linux environment. This is usually something like: <code class=\"language-plaintext highlighter-rouge\">/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe</code>. The PowerShell script is in Windows format with escaped backslashes.</p>\n<pre><code class=\"language-env\">API_KEY = \"your_api_key\"\nWORK_LATITUDE = \"work_latitude\"\nWORK_LONGITUDE = \"work_longitude\"\nHOME_LATITUDE = \"home_latitude\"\nHOME_LONGITUDE = \"home_longitude\"\n\nPOWERSHELL_PATH = \"/mnt/c/path/to/powershell.exe\"\nPOWERSHELL_SCRIPT = \"C:\\\\windows\\\\path\\\\to\\\\show_notification.ps1\"\n</code></pre>\n<h2 id=\"creating-a-conda-environment-to-run-the-script\">Creating a conda environment to run the script</h2>\n<p>To run the Python script I used a conda environment. It can be created and activated with:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda <span class=\"nb\">env </span>create <span class=\"nt\">-f</span> environment.yml\nconda activate traffic-widget\n</code></pre></div></div>\n<h2 id=\"running-the-script\">Running the script</h2>\n<p>Finally I needed some Python code to run the whole process:</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">\"__main__\"</span><span class=\"p\">:</span>\n    <span class=\"n\">load_dotenv</span><span class=\"p\">()</span>\n    <span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"API_KEY\"</span><span class=\"p\">]</span>\n    <span class=\"n\">work_lat</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"WORK_LATITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">work_lon</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"WORK_LONGITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">home_lat</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"HOME_LATITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">home_lon</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"s\">\"HOME_LONGITUDE\"</span><span class=\"p\">])</span>\n    <span class=\"n\">powershell_path</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s\">\"POWERSHELL_PATH\"</span><span class=\"p\">)</span>\n    <span class=\"n\">powershell_script</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s\">\"POWERSHELL_SCRIPT\"</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">powershell_script</span><span class=\"p\">:</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Error: POWERSHELL_SCRIPT environment variable not set.\"</span><span class=\"p\">)</span>\n        <span class=\"nb\">exit</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n    <span class=\"n\">tomtom_api</span> <span class=\"o\">=</span> <span class=\"n\">TomTomAPI</span><span class=\"p\">(</span><span class=\"n\">api_key</span><span class=\"p\">)</span>\n    <span class=\"n\">notifier</span> <span class=\"o\">=</span> <span class=\"n\">WindowsNotifier</span><span class=\"p\">(</span><span class=\"n\">powershell_path</span><span class=\"p\">,</span> <span class=\"n\">powershell_script</span><span class=\"p\">)</span>\n\n    <span class=\"n\">home_travel_time</span> <span class=\"o\">=</span> <span class=\"n\">tomtom_api</span><span class=\"p\">.</span><span class=\"n\">get_travel_time</span><span class=\"p\">(</span>\n        <span class=\"n\">work_lat</span><span class=\"p\">,</span> <span class=\"n\">work_lon</span><span class=\"p\">,</span> <span class=\"n\">home_lat</span><span class=\"p\">,</span> <span class=\"n\">home_lon</span>\n    <span class=\"p\">)</span>\n\n    <span class=\"n\">message_lines</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"n\">format_travel_time</span><span class=\"p\">(</span><span class=\"s\">\"Home\"</span><span class=\"p\">,</span> <span class=\"n\">home_travel_time</span><span class=\"p\">),</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">notification_message</span> <span class=\"o\">=</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">message_lines</span><span class=\"p\">)</span>\n\n    <span class=\"n\">notifier</span><span class=\"p\">.</span><span class=\"n\">show_notification</span><span class=\"p\">(</span><span class=\"s\">\"Driving times:\"</span><span class=\"p\">,</span> <span class=\"n\">notification_message</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>Manually running the script with <code class=\"language-plaintext highlighter-rouge\">python traffic-widget.py</code> should cause the following pop-up:</p>\n<p><img alt=\"An example notification\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/traffic_widget/traffic_notification.png\"/></p>\n<h2 id=\"setting-up-a-cron-job-to-automatically-run-the-script\">Setting up a cron job to automatically run the script</h2>\n<p>To make the script run automatically, back in WSL, I ran:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>crontab <span class=\"nt\">-e</span>\n</code></pre></div></div>\n<p>And added the details for the cron job. I wanted mine to run every 5 minutes from 4-5 pm on weekdays.</p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>0-55/5 16 * * 1-5 /home/<username>/miniconda3/envs/traffic-widget/bin/python /home/<username>/path/to/traffic-widget/traffic-widget.py\n</username></username></code></pre></div></div>","doi":"https://doi.org/10.59350/8kc3w-r1780","guid":"https://jonswain.github.io/building-a-traffic-widget","image":"https://jonswain.github.io/images/traffic_widget/traffic_notification.png","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1746057600,"rid":"zh250-47j23","summary":"No data science or cheminformatics today! I usually cycle or get public transport to work, but occasionally I do have to drive. The traffic in T\u0101maki Makaurau (Auckland) is very variable, especially on rainy or windy days, so I often find myself checking Google Maps every 5 minutes after 4 pm to work out when I need to leave to get home.","tags":["Software-projects"],"title":"Building a Traffic Reminder Widget","updated_at":1788763524,"url":"https://jonswain.github.io/building-a-traffic-reminder-widget/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>An unwelcome cyclone may have cancelled my hiking plans, but it gave me the opportunity to delve into the fascinating world of drug repurposing. This blog post explores how identifying new uses for existing drugs can dramatically accelerate and reduce the cost of bringing vital treatments to patients, and how recent advances in machine learning can further streamline this process.</p>\n<hr/>\n<p>Over the Easter long weekend, T\u0101maki Makaurau (Auckland) was visited by Cyclone Tam, which meant my hiking trip was cancelled. At least it gave me some time to work on a few projects and finish this blog post I'd been meaning to do for a while!</p>\n<h2 id=\"drug-repurposing\">Drug Repurposing</h2>\n<p>Traditional drug discovery is a marathon, often taking over a decade and requiring an investment of more than a billion dollars for each successful drug. This lengthy and costly process highlights the urgent need for more efficient strategies to combat disease. One such promising strategy is drug repurposing: identifying new therapeutic uses for existing drugs or those already undergoing clinical trials. By bypassing much of the early-stage development, and sometimes significant parts of clinical trials, drug repurposing offers the potential to significantly reduce both the time and financial cost associated with bringing new treatments to patients. Drug repurposing is ideal for addressing therapeutic needs in disease areas where financial incentives are limited (such as neglected tropical diseases and antibiotic development), or for rapid deployment in situations demanding swift treatment interventions for imminent public health emergencies, such as during the COVID-19 pandemic.</p>\n<p>One well-known example of drug repurposing is sildenafil, originally developed to treat high blood pressure and angina (chest pain), and famously repurposed for erectile dysfunction after clinical trials revealed this unexpected side effect. Another is thalidomide, developed to treat morning sickness and found to cause birth defects, which is now used to treat cancers and skin disorders associated with leprosy. The crucial role of drug repurposing was further highlighted during the recent COVID-19 pandemic, where the corticosteroid dexamethasone emerged as a vital intervention in reducing mortality among severely ill patients.</p>\n<p>Machine learning offers a novel method for drug repurposing. Using high-quality activity data for a target of interest, a machine learning model can be trained to predict active compounds based on their molecular structure. This trained model can then be used to screen libraries of compounds for potential repurposing.</p>\n<h2 id=\"the-drug-repurposing-hub\">The Drug Repurposing Hub</h2>\n<p>The <a href=\"https://www.nature.com/articles/nm.4306.epdf\">Drug Repurposing Hub</a> is a curated and annotated collection of drugs that have already received FDA approval or are currently undergoing clinical trials. Maintained by the Broad Institute, this resource serves as both a virtual library, providing comprehensive information on each compound, and a physical library, offering access to compound plates for experimental screening.</p>\n<h2 id=\"antibiotic-discovery-using-deep-learning\">Antibiotic Discovery using Deep Learning</h2>\n<p>This post was inspired by the work of the <a href=\"https://www.cell.com/cell/fulltext/S0092-8674(20)30102-1\">Collins Lab at MIT</a>, who used deep learning techniques to identify potential compounds for repurposing as treatments for antimicrobial-resistant bacteria. Their research addresses the escalating crisis of antibiotic resistance, a looming threat projected to cause 10 million deaths annually by 2050.</p>\n<p>To tackle this challenge, the researchers assembled a primary training set of 2,335 molecules, experimentally screening them against <em>E. coli</em> BW25113 and identifying 120 compounds with antimicrobial activity. With the goal of creating a robust and generalizable model, they ensured the training set was structurally diverse. This data was then used to train a directed message-passing neural network (dMPNN) to create a classification model capable of predicting antimicrobial activity based on a molecule's chemical structure. The trained model was subsequently used to make predictions on virtual libraries of compounds, including the Drug Repurposing Hub.</p>\n<p>Ninety-nine molecules from the Drug Repurposing Hub that were predicted to be active were experimentally tested, with 51 displaying growth inhibition against <em>E. coli</em>. This process led to the identification of a particularly promising compound, the c-Jun N-terminal kinase inhibitor SU3327, which the researchers named halicin. Notably, halicin possesses a structure distinctly different from conventional antibiotics and demonstrated potent inhibitory activity against <em>E. coli</em> growth.</p>\n<h2 id=\"chemprop\">Chemprop</h2>\n<p>Central to this research is the open-source software package <a href=\"https://chemprop.readthedocs.io/en/latest/\">Chemprop</a>. Developed primarily by researchers at MIT using the PyTorch framework, Chemprop harnesses the capabilities of message-passing neural networks (MPNNs) for molecular property prediction. Chemprop can be used both as a command-line interface (CLI) and a Python API. It implements a d-MPNN architecture, a type of graph neural network particularly effective for extracting relationships from molecular structures represented as graphs. An advantage of Chemprop is its ability to learn directly from molecular representations, such as SMILES strings converted into molecular graphs, eliminating the need for manual feature engineering, which can also introduce biases.</p>\n<h2 id=\"chembl\">ChEMBL</h2>\n<p>Lacking access to a physical lab for experimental data, I needed an alternative approach for data gathering. For this, I used ChEMBL, a freely accessible and meticulously curated chemical database housing bioactivity data for drug-like molecules. It is maintained by the European Bioinformatics Institute (EBI), part of the European Molecular Biology Laboratory (EMBL) in the UK. Using the <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC4489243/\">ChEMBL Webservice</a>, I was able to specifically download data relevant to the target of interest. This retrieved data then served as the foundational dataset for training a machine learning model, which was subsequently employed to virtually screen the compounds within The Drug Repurposing Hub.</p>\n<h2 id=\"imports\">Imports</h2>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">chembl_webresource_client.new_client</span> <span class=\"kn\">import</span> <span class=\"n\">new_client</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">Draw</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"models\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"getting-the-data-from-chembl\">Getting the Data from ChEMBL</h2>\n<p>Using the ChEMBL Webservice, I downloaded the data associated with a target for training a machine learning model using the three functions below. The first searches ChEMBL with a target name, and returns the target ChEMBL ID of the most similar target. The second downloads the activity data associated that the target ChEMBL ID, along with the molecular ChEMBL IDs. The third returns the canonical SMILES for the molecular ChEMBL IDs.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">target</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">target</span>\n<span class=\"n\">activity</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">activity</span>\n<span class=\"n\">molecule</span> <span class=\"o\">=</span> <span class=\"n\">new_client</span><span class=\"p\">.</span><span class=\"n\">molecule</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_target_chembl_id</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the ChEMBL ID of a target given its name.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Searching for target: \"</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"'</span><span class=\"p\">)</span>\n    <span class=\"n\">target_query</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">search</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">)</span>\n    <span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"n\">chembl_id</span> <span class=\"o\">=</span> <span class=\"n\">target_query</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"pref_name\"</span><span class=\"p\">],</span> <span class=\"n\">target_query</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"s\">\"target_chembl_id\"</span><span class=\"p\">]</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Target name: </span><span class=\"si\">{</span><span class=\"n\">name</span><span class=\"si\">}</span><span class=\"s\">, ChEMBL ID: </span><span class=\"si\">{</span><span class=\"n\">chembl_id</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">chembl_id</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_target_activity</span><span class=\"p\">(</span><span class=\"n\">chembl_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the activity data for a target given its ChEMBL ID.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Getting activity data for </span><span class=\"si\">{</span><span class=\"n\">chembl_id</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">activities</span> <span class=\"o\">=</span> <span class=\"n\">activity</span><span class=\"p\">.</span><span class=\"nb\">filter</span><span class=\"p\">(</span>\n        <span class=\"n\">target_chembl_id</span><span class=\"o\">=</span><span class=\"n\">chembl_id</span><span class=\"p\">,</span>\n        <span class=\"n\">standard_type</span><span class=\"o\">=</span><span class=\"s\">\"IC50\"</span><span class=\"p\">,</span>\n        <span class=\"n\">relation</span><span class=\"o\">=</span><span class=\"s\">\"=\"</span><span class=\"p\">,</span>\n        <span class=\"n\">standard_units</span><span class=\"o\">=</span><span class=\"s\">\"nM\"</span><span class=\"p\">,</span>\n    <span class=\"p\">).</span><span class=\"n\">only</span><span class=\"p\">(</span>\n        <span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"standard_value\"</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Found </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">activities</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> activities\"</span><span class=\"p\">)</span>\n    <span class=\"n\">activities_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span>\n        <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">activities</span><span class=\"p\">[:</span><span class=\"mi\">100</span><span class=\"p\">],</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Processing Activities\"</span><span class=\"p\">))</span>\n    <span class=\"p\">)[[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"standard_value\"</span><span class=\"p\">]]</span>\n    <span class=\"n\">activities_df</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">to_numeric</span><span class=\"p\">(</span><span class=\"n\">activities_df</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">])</span>\n    <span class=\"k\">return</span> <span class=\"n\">activities_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">get_molecule_data</span><span class=\"p\">(</span><span class=\"n\">molecule_chembl_ids</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Get the molecule data for a list of ChEMBL IDs.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Getting molecule data for </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">molecule_chembl_ids</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\"> ChEMBL IDs\"</span><span class=\"p\">)</span>\n    <span class=\"n\">compounds_provider</span> <span class=\"o\">=</span> <span class=\"n\">molecule</span><span class=\"p\">.</span><span class=\"nb\">filter</span><span class=\"p\">(</span>\n        <span class=\"n\">molecule_chembl_id__in</span><span class=\"o\">=</span><span class=\"n\">molecule_chembl_ids</span>\n    <span class=\"p\">).</span><span class=\"n\">only</span><span class=\"p\">(</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"molecule_structures\"</span><span class=\"p\">)</span>\n    <span class=\"n\">compounds_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">json_normalize</span><span class=\"p\">(</span>\n        <span class=\"nb\">list</span><span class=\"p\">(</span><span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"n\">compounds_provider</span><span class=\"p\">,</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Processing Compounds\"</span><span class=\"p\">))</span>\n    <span class=\"p\">)[[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span> <span class=\"s\">\"molecule_structures.canonical_smiles\"</span><span class=\"p\">]].</span><span class=\"n\">rename</span><span class=\"p\">(</span>\n        <span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s\">\"molecule_structures.canonical_smiles\"</span><span class=\"p\">:</span> <span class=\"s\">\"canonical_smiles\"</span><span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">compounds_df</span>\n</code></pre></div></div>\n<h2 id=\"format-the-data-for-chemprop\">Format the Data for Chemprop</h2>\n<p>Chemprop takes the data in the form of a table containing one column of SMILES strings, and one columns of activities. For this example I am creating a classification model, so the activity column is a binary classification (1 or 0). The first function takes the activity data and molecule data and merges them on the molecule ChEMBL ID, before cleaning the data up and creating the binary variable based on a cutoff. This cutoff can be changed depending on the target and dataset. I've always found the CLI usage of Chemprop easier, so the data is saved as a CSV file for training.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">create_training_data</span><span class=\"p\">(</span>\n    <span class=\"n\">activity_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">molecule_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Create the training data for the model.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Creating training data\"</span><span class=\"p\">)</span>\n    <span class=\"n\">training_data</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"p\">(</span>\n            <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">merge</span><span class=\"p\">(</span>\n                <span class=\"n\">left</span><span class=\"o\">=</span><span class=\"n\">activity_data</span><span class=\"p\">,</span>\n                <span class=\"n\">right</span><span class=\"o\">=</span><span class=\"n\">molecule_data</span><span class=\"p\">,</span>\n                <span class=\"n\">on</span><span class=\"o\">=</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">,</span>\n                <span class=\"n\">how</span><span class=\"o\">=</span><span class=\"s\">\"left\"</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">how</span><span class=\"o\">=</span><span class=\"s\">\"any\"</span><span class=\"p\">)</span>\n            <span class=\"p\">.</span><span class=\"n\">drop</span><span class=\"p\">(</span><span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">)</span>\n        <span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"canonical_smiles\"</span><span class=\"p\">,</span> <span class=\"n\">as_index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">agg</span><span class=\"p\">(</span><span class=\"s\">\"mean\"</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">]</span> <span class=\"o\">&lt;</span> <span class=\"n\">nm_cutoff</span><span class=\"p\">).</span><span class=\"n\">astype</span><span class=\"p\">(</span>\n        <span class=\"nb\">int</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">actives</span> <span class=\"o\">=</span> <span class=\"n\">training_data</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">].</span><span class=\"nb\">sum</span><span class=\"p\">()</span>\n    <span class=\"n\">inactives</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">)</span> <span class=\"o\">-</span> <span class=\"n\">actives</span>\n    <span class=\"n\">percentage</span> <span class=\"o\">=</span> <span class=\"mi\">100</span> <span class=\"o\">*</span> <span class=\"n\">actives</span> <span class=\"o\">/</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Actives: </span><span class=\"si\">{</span><span class=\"n\">actives</span><span class=\"si\">}</span><span class=\"s\"> (</span><span class=\"si\">{</span><span class=\"n\">percentage</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">%), Inactives: </span><span class=\"si\">{</span><span class=\"n\">inactives</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">training_data</span><span class=\"p\">.</span><span class=\"n\">drop</span><span class=\"p\">(</span><span class=\"n\">columns</span><span class=\"o\">=</span><span class=\"s\">\"standard_value\"</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">save_training_data</span><span class=\"p\">(</span>\n    <span class=\"n\">training_data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"p\">:</span> <span class=\"n\">Path</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Save the training data to a CSV file.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Saving training data to </span><span class=\"si\">{</span><span class=\"n\">output_dir</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">output_dir</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">parents</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> <span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">training_data</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"n\">output_dir</span> <span class=\"o\">/</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">_training_data.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Training data saved to </span><span class=\"si\">{</span><span class=\"n\">output_dir</span> <span class=\"o\">/</span> <span class=\"sa\">f</span><span class=\"s\">'</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"n\">_training_data</span><span class=\"p\">.</span><span class=\"n\">csv</span><span class=\"s\">'</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"download-the-drug-repurposing-hub-dataset\">Download the Drug Repurposing Hub dataset</h2>\n<p>This function downloaded the Drug Repurposing Hub dataset, tidies the data up and saves it for making predictions using Chemprop.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">canon_smiles</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span> <span class=\"o\">|</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Convert SMILES string to canonical SMILES.\"\"\"</span>\n    <span class=\"n\">mol</span> <span class=\"o\">=</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">smiles</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">mol</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"bp\">None</span>\n    <span class=\"k\">return</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolToSmiles</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">,</span> <span class=\"n\">canonical</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">download_drug_repurposing_hub</span><span class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Download the Drug Repurposing Hub dataset.\"\"\"</span>\n    <span class=\"k\">if</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">).</span><span class=\"n\">exists</span><span class=\"p\">():</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Drug Repurposing Hub dataset already downloaded\"</span><span class=\"p\">)</span>\n        <span class=\"k\">return</span> <span class=\"bp\">None</span>\n    <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">\"https://storage.googleapis.com/cdot-general-storage/repurposing_samples_20240610.txt\"</span>\n    <span class=\"n\">drug_repurposing_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"n\">url</span><span class=\"p\">,</span>\n        <span class=\"n\">sep</span><span class=\"o\">=</span><span class=\"s\">\"</span><span class=\"se\">\\t</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"n\">skiprows</span><span class=\"o\">=</span><span class=\"mi\">9</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">].</span><span class=\"nb\">apply</span><span class=\"p\">(</span><span class=\"n\">canon_smiles</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">],</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">drop_duplicates</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">],</span> <span class=\"n\">keep</span><span class=\"o\">=</span><span class=\"s\">\"first\"</span><span class=\"p\">,</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">drug_repurposing_df</span><span class=\"p\">.</span><span class=\"n\">to_csv</span><span class=\"p\">(</span><span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Drug Repurposing Hub dataset downloaded and processed\"</span><span class=\"p\">)</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Number of unique SMILES: </span><span class=\"si\">{</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">drug_repurposing_df</span><span class=\"p\">[</span><span class=\"s\">'smiles'</span><span class=\"p\">].</span><span class=\"n\">unique</span><span class=\"p\">())</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"make-predictions-on-the-drug-repurposing-hub-data\">Make Predictions on the Drug Repurposing Hub Data</h2>\n<p>As I mentioned above, I find the CLI usage of Chemprop easier (especially since the move from v1 to v2), so here I'm using subprocess to run Chemprop. These two functions train a Chemprop model, and make predictions on the Drug Repurposing Hub data.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">data_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train a ChemProp model using the training data.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Training ChemProp model for </span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">chemprop_train_args</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"train\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--data-path\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">data_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_training_data.csv\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--task-type\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"classification\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--save-dir\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">output_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--split-type\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"scaffold_balanced\"</span><span class=\"p\">,</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">chemprop_train_args</span><span class=\"p\">,</span> <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">predict_with_chemprop</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">preds_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">model_dir</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Predict using the ChemProp model.\"\"\"</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Predicting with ChemProp model for </span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n    <span class=\"n\">chemprop_predict_args</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"predict\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"data/drug_repurposing_hub.csv\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--model-paths\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">model_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--smiles-columns\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"smiles\"</span><span class=\"p\">,</span>\n        <span class=\"s\">\"--preds-path\"</span><span class=\"p\">,</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">preds_dir</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">target_name</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_predictions.csv\"</span><span class=\"p\">,</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">chemprop_predict_args</span><span class=\"p\">,</span> <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"visualize-the-top-predictions\">Visualize the Top Predictions</h2>\n<p>Once we've scored the compounds, we want to take a look at the top scoring compounds.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Visualize the top predictions.\"\"\"</span>\n    <span class=\"n\">predictions_df</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">search</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s\">_predictions.csv\"</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"activity\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">head</span><span class=\"p\">(</span><span class=\"mi\">9</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">Draw</span><span class=\"p\">.</span><span class=\"n\">MolsToGridImage</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span><span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">s</span> <span class=\"ow\">in</span> <span class=\"n\">predictions_df</span><span class=\"p\">[</span><span class=\"s\">\"smiles\"</span><span class=\"p\">]],</span>\n        <span class=\"n\">legends</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">a</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">2</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span> <span class=\"k\">for</span> <span class=\"n\">a</span> <span class=\"ow\">in</span> <span class=\"n\">predictions_df</span><span class=\"p\">[</span><span class=\"s\">\"activity\"</span><span class=\"p\">]],</span>\n        <span class=\"n\">molsPerRow</span><span class=\"o\">=</span><span class=\"mi\">3</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"create-a-drug-repurposing-pipeline\">Create a Drug Repurposing Pipeline</h2>\n<p>This function runs the whole process for a specified target search.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"mi\">25</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run the drug repurposing pipeline.\"\"\"</span>\n    <span class=\"n\">download_drug_repurposing_hub</span><span class=\"p\">()</span>\n    <span class=\"n\">target_chembl_id</span> <span class=\"o\">=</span> <span class=\"n\">get_target_chembl_id</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n    <span class=\"n\">activity_data</span> <span class=\"o\">=</span> <span class=\"n\">get_target_activity</span><span class=\"p\">(</span><span class=\"n\">target_chembl_id</span><span class=\"p\">)</span>\n    <span class=\"n\">molecule_data</span> <span class=\"o\">=</span> <span class=\"n\">get_molecule_data</span><span class=\"p\">(</span><span class=\"n\">activity_data</span><span class=\"p\">[</span><span class=\"s\">\"molecule_chembl_id\"</span><span class=\"p\">].</span><span class=\"n\">tolist</span><span class=\"p\">())</span>\n    <span class=\"n\">training_data</span> <span class=\"o\">=</span> <span class=\"n\">create_training_data</span><span class=\"p\">(</span>\n        <span class=\"n\">activity_data</span><span class=\"p\">,</span> <span class=\"n\">molecule_data</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"n\">nm_cutoff</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">save_training_data</span><span class=\"p\">(</span><span class=\"n\">training_data</span><span class=\"p\">,</span> <span class=\"n\">search</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">\" \"</span><span class=\"p\">,</span> <span class=\"s\">\"_\"</span><span class=\"p\">),</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">))</span>\n    <span class=\"n\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"o\">=</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">data_dir</span><span class=\"o\">=</span><span class=\"s\">\"data\"</span><span class=\"p\">,</span> <span class=\"n\">output_dir</span><span class=\"o\">=</span><span class=\"s\">\"models/\"</span><span class=\"p\">)</span>\n    <span class=\"n\">predict_with_chemprop</span><span class=\"p\">(</span><span class=\"n\">target_name</span><span class=\"o\">=</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">preds_dir</span><span class=\"o\">=</span><span class=\"s\">\"data\"</span><span class=\"p\">,</span> <span class=\"n\">model_dir</span><span class=\"o\">=</span><span class=\"s\">\"models/\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<h2 id=\"run-the-drug-repurposing-pipeline\">Run the Drug Repurposing Pipeline</h2>\n<h3 id=\"malaria\">Malaria</h3>\n<p>Malaria kills around half a million people per year, with 76% of global malaria deaths in children under 5 years old. It is considered a neglected tropical disease (NTD) due to its significant impact on global health, particularly in low-income countries. It disproportionately affects some of the world's poorest people, and is often overlooked in global health efforts. Drug repurposing could offer alternative therapies significantly cheaper than developing an entirely new drug. Here I look to target the most deadly species of parasite that carries malaria - <em>Plasmodium falciparum</em>. After training the Chemprop model and screening the Drug Repurposing Hub, some compounds are flagged as potential hits and could be investigated experimentally.</p>\n<p>Of the top 10 predictions, <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC10545508/\">pyronaridine</a> and <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC4135840/\">KAF-156</a> are both malaria treatments, and were both in the training data, but interestingly pyronaridine was not classified as a hit in the training data.\n<a href=\"https://pubmed.ncbi.nlm.nih.gov/26366636/\">Carfilzomib</a>, <a href=\"https://www.sciencedirect.com/science/article/abs/pii/S0162013403002538\">TPPS4</a>, <a href=\"https://www.nature.com/articles/s41419-020-03017-4\">nanchangmycin</a>, <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC5554889/\">oprozomib</a>, and <a href=\"https://iris.who.int/handle/10665/64651\">lindane</a> have all been investigated for treating malaria, so the model appears to be picking out the right compounds. <a href=\"https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0288335\">Abemaciclib</a> appears to have been recently investigated for repurposing with malaria. <a href=\"https://www.medchemexpress.com/GS967.html?srsltid=AfmBOop_hg1DsKWTwXXujU9XuCISm0oM2Vchpa5gDLKXUABjR9GkmTqv\">GS-967</a> appears to have no link to malaria yet, but maybe should be investigated experimentally!</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">search</span> <span class=\"o\">=</span> <span class=\"s\">\"Plasmodium falciparum\"</span>\n\n<span class=\"n\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n<span class=\"n\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Drug Repurposing Hub dataset downloaded and processed\nNumber of unique SMILES: 6734\nSearching for target: \"Plasmodium falciparum\"\nTarget name: Plasmodium falciparum, ChEMBL ID: CHEMBL364\nGetting activity data for CHEMBL364\nFound 45314 activities\nProcessing Activities: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 45314/45314 [00:02&lt;00:00, 15928.83it/s]\nGetting molecule data for 45314 ChEMBL IDs\nProcessing Molecules: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 21821/21821 [1:32:46&lt;00:00,  3.92it/s]\nCreating training data\nActives: 2677 (12.30%), Inactives: 19082\nSaving training data to data\nTraining data saved to data/Plasmodium_falciparum_training_data.csv\nTraining ChemProp model for Plasmodium falciparum\nPredicting with ChemProp model for Plasmodium falciparum\n</code></pre></div></div>\n<p><img alt=\"Grid of 2D chemical structures for the top 10 compounds the Chemprop model predicted as Plasmodium falciparum hits\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/drug_repurposing/malaria-hits.png\"/></p>\n<h3 id=\"covid-19\">COVID-19</h3>\n<p>The COVID-19 pandemic has caused the deaths of over 7 million people so far, and in 2020 more than half of the world's population was under some form of lockdown. With the speed that COVID spread around the globe, there was no time to develop new drug from scratch, and drug repurposing was explored in clinics to find treatments. In this example, I didn't find any promising compounds in the Drug Repurposing Hub, but with a much smaller dataset than for malaria, the machine learning model is potentially much less powerfull.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">search</span> <span class=\"o\">=</span> <span class=\"s\">\"SARS-CoV-2\"</span>\n\n<span class=\"n\">run_repurposing_pipeline</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">,</span> <span class=\"n\">nm_cutoff</span><span class=\"o\">=</span><span class=\"mi\">500</span><span class=\"p\">)</span>\n<span class=\"n\">visualize_top_predictions</span><span class=\"p\">(</span><span class=\"n\">search</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Drug Repurposing Hub dataset already downloaded\nSearching for target: \"SARS-CoV-2\"\nTarget name: SARS-CoV-2, ChEMBL ID: CHEMBL4303835\nGetting activity data for CHEMBL4303835\nFound 802 activities\nProcessing Activities: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 802/802 [00:00&lt;00:00, 12406.99it/s]\nGetting molecule data for 802 ChEMBL IDs\nProcessing Compounds: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 490/490 [00:00&lt;00:00, 6081.22it/s]\nCreating training data\nActives: 71 (14.55%), Inactives: 417\nSaving training data to data\nTraining data saved to data/SARS-CoV-2_training_data.csv\nTraining ChemProp model for SARS-CoV-2\nPredicting with ChemProp model for SARS-CoV-2\n</code></pre></div></div>\n<p><img alt=\"Grid of 2D chemical structures for the top 10 compounds the Chemprop model predicted as SARS-CoV-2 hits\" class=\"img-responsive center-image\" src=\"https://jonswain.github.io/images/drug_repurposing/covid-hits.png\"/></p>","doi":"https://doi.org/10.59350/qpafc-nt106","guid":"https://jonswain.github.io/drug-repurposing-using-ai","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1746230400,"rid":"yj9hq-h3j13","summary":"An unwelcome cyclone may have cancelled my hiking plans, but it gave me the opportunity to delve into the fascinating world of drug repurposing. This blog post explores how identifying new uses for existing drugs can dramatically accelerate and reduce the cost of bringing vital treatments to patients, and how recent advances in machine learning can further streamline this process.","tags":["Ai","Cheminformatics","Data-science","Drug-repurposing","Machine-learning"],"title":"Drug Repurposing Using Artificial Intelligence","updated_at":1788763519,"url":"https://jonswain.github.io/drug-repurposing-using-artificial-intelligence/","version":"v1"},{"authors":[{"contributor_roles":[],"family":"Swain","given":"Jonathan","url":"https://orcid.org/0000-0003-4457-1481"}],"blog":{"authors":[{"name":"Jon Swain","url":"https://orcid.org/0000-0003-4457-1481"}],"community_id":"13f55986-f209-443c-ae0d-2f9f3f521e5a","created":1788652800,"current_feed_url":null,"description":"I am a cheminformatician and data scientist, originally from the UK, but often found in Aotearoa (New Zealand). I'm interested in using data science and machine learning to solve problems in drug discovery.","doi":"https://doi.org/10.59350/jonswain","favicon":"https://rogue-scholar.org/api/communities/13f55986-f209-443c-ae0d-2f9f3f521e5a/logo","feed_format":"application/atom+xml","feed_url":"https://jonswain.github.io/feed.xml","filter":null,"generator":"Jekyll","home_page_url":"https://jonswain.github.io/","issn":null,"language":"eng","license":"https://creativecommons.org/licenses/by/4.0/legalcode","prefix":"10.59350","relative_url":null,"secure":true,"slug":"jonswain","status":"active","subfield":"3002","title":"Jon Swain","updated":1778680800,"use_api":null},"blog_name":"Jon Swain","blog_slug":"jonswain","content_html":"<p>Whilst neural networks (<strong>NNs</strong>) have done amazing things with unstructured data such as text and images, they've traditionally been outperformed on tabular data by Gradient-Boosted Decision Trees (<strong>GBDTs</strong>), although recent advances such as <a href=\"https://github.com/PriorLabs/TabPFN\">TabPFN</a> and <a href=\"https://github.com/soda-inria/tabicl\">TabICL</a> suggest that the performance gap may have closed. <a href=\"https://jonswain.github.io/tabpfn-for-chemical-datasets/\">I've written a bit about TabPFN for chemical datasets here</a>.</p>\n<p>One of the most popular deep learning architectures for chemical property prediction is <a href=\"https://github.com/chemprop/chemprop\">Chemprop</a>, developed by researchers at MIT. A Chemprop model is made up of two NNs. The first is a directed Message Passing Neural Network (<strong>d-MPNN</strong>), a type of Graph Convolutional Neural Network (<strong>GCNN</strong>), that takes a graphical representation of a molecule and converts it to a <strong>molecular embedding</strong>, a vector that describes the original molecule. This molecular embedding is then put through a Feed-Forward Neural Network (<strong>FFN</strong>), a fully connected NN used to make the final prediction. The model is trained end-to-end, with both NNs being updated at the same time to minimise a loss function.</p>\n<p>Once the model is trained, the <strong>d-MPNN</strong> can be used to calculate a molecular embedding for a collection of molecules. This is a learned representation optimised for the specific chemical prediction problem. This learned representation is a vector with a length equal to the number of input nodes in the FFN. This is very similar to a traditional molecular fingerprint, such as a <strong>Morgan fingerprint</strong>.</p>\n<p>A collection of these learned fingerprints forms a 2-dimensional array, which is <strong>tabular data</strong>. Since GBDTs regularly outperform NNs on tabular data, can we improve the performance of Chemprop by replacing the FFN with a GBDT after training the d-MPNN?</p>\n<p>Whilst writing this post, a paper was published in <em>The Journal of Chemical Information and Modeling</em> by Pat Walters et al., <a href=\"https://doi.org/10.1021/acs.jcim.5c01609\">Practically Significant Method Comparison Protocols for Machine Learning in Small Molecule Drug Discovery</a>. This is probably my favourite cheminformatics paper so far this year, with comprehensive guidelines on how to compare machine learning models in a statistically robust way. I've tried to follow these recommendations when comparing models here.</p>\n<p>Below I compare three models: the first is a Random Forest (this is not gradient-boosted, but provides a good baseline without hyperparameter tuning) trained on Morgan fingerprints; the second is a standard Chemprop model; and the third generates learned fingerprints using the Chemprop d-MPNN and then trains a Random Forest model on these. I used two datasets, one for a classification problem and one for a regression problem. The classification data is the <a href=\"https://doi.org/10.1021/acsomega.3c01583\">Bile Salt Export Pump dataset</a> and the regression data is the <a href=\"https://polarishub.io/datasets/biogen/adme-fang-v1\">Polaris LogS dataset</a>, both ideas 'borrowed' from <a href=\"https://practicalcheminformatics.blogspot.com\">Practical Cheminformatics posts</a>.</p>\n<h2 id=\"imports\">Imports</h2>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">subprocess</span>\n<span class=\"kn\">from</span> <span class=\"nn\">collections</span> <span class=\"kn\">import</span> <span class=\"n\">defaultdict</span>\n<span class=\"kn\">from</span> <span class=\"nn\">pathlib</span> <span class=\"kn\">import</span> <span class=\"n\">Path</span>\n<span class=\"kn\">from</span> <span class=\"nn\">typing</span> <span class=\"kn\">import</span> <span class=\"n\">Callable</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">matplotlib.pyplot</span> <span class=\"k\">as</span> <span class=\"n\">plt</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n<span class=\"kn\">import</span> <span class=\"nn\">pandas</span> <span class=\"k\">as</span> <span class=\"n\">pd</span>\n<span class=\"kn\">import</span> <span class=\"nn\">seaborn</span> <span class=\"k\">as</span> <span class=\"n\">sns</span>\n<span class=\"kn\">from</span> <span class=\"nn\">numpy.typing</span> <span class=\"kn\">import</span> <span class=\"n\">NDArray</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit</span> <span class=\"kn\">import</span> <span class=\"n\">Chem</span>\n<span class=\"kn\">from</span> <span class=\"nn\">rdkit.Chem</span> <span class=\"kn\">import</span> <span class=\"n\">rdFingerprintGenerator</span>\n<span class=\"kn\">from</span> <span class=\"nn\">scipy.stats</span> <span class=\"kn\">import</span> <span class=\"n\">f_oneway</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.base</span> <span class=\"kn\">import</span> <span class=\"n\">BaseEstimator</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.ensemble</span> <span class=\"kn\">import</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">,</span> <span class=\"n\">RandomForestRegressor</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.metrics</span> <span class=\"kn\">import</span> <span class=\"p\">(</span>\n    <span class=\"n\">matthews_corrcoef</span><span class=\"p\">,</span>\n    <span class=\"n\">mean_absolute_error</span><span class=\"p\">,</span>\n    <span class=\"n\">r2_score</span><span class=\"p\">,</span>\n    <span class=\"n\">root_mean_squared_error</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">ShuffleSplit</span>\n<span class=\"kn\">from</span> <span class=\"nn\">statsmodels.stats.multicomp</span> <span class=\"kn\">import</span> <span class=\"n\">pairwise_tukeyhsd</span>\n<span class=\"kn\">from</span> <span class=\"nn\">tqdm.notebook</span> <span class=\"kn\">import</span> <span class=\"n\">tqdm</span>\n\n<span class=\"kn\">from</span> <span class=\"nn\">visualisation</span> <span class=\"kn\">import</span> <span class=\"n\">confusion_matricies</span><span class=\"p\">,</span> <span class=\"n\">scatter_plots</span>\n</code></pre></div></div>\n<h2 id=\"useful-functions\">Useful functions</h2>\n<p>The first function <code class=\"language-plaintext highlighter-rouge\">prepare_data</code> takes the path to a CSV file, creates RDKit molecular objects, removes any that are causing issues, and calculates the Morgan fingerprints for each model.</p>\n<p>The second, <code class=\"language-plaintext highlighter-rouge\">save_data_for_chemprop</code>, formats the data and saves it for training Chemprop models.</p>\n<p>The next three functions: <code class=\"language-plaintext highlighter-rouge\">train_chemprop_model</code>, <code class=\"language-plaintext highlighter-rouge\">make_chemprop_predictions</code>, and <code class=\"language-plaintext highlighter-rouge\">calculate_chemprop_fingerprints</code>, all use subprocess to call the Chemprop CLI. I've generally found it easier to use than the Chemprop Python module.</p>\n<p>The next two: <code class=\"language-plaintext highlighter-rouge\">train_morgan_sklearn_model</code> and <code class=\"language-plaintext highlighter-rouge\">train_chemprop_sklearn_model</code>, use Scikit-learn to train Random Forest models, the first using the previously generated Morgan fingerprints, and the second uses the Chemprop learned fingerprints.</p>\n<p>Finally, <code class=\"language-plaintext highlighter-rouge\">gather_predictions</code> uses all of the trained models to make predictions on a test dataset.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">prepare_data</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">:</span> <span class=\"n\">Path</span><span class=\"p\">,</span> <span class=\"n\">smiles_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">]:</span>\n    <span class=\"s\">\"\"\"Load and clean data, generate Morgan fingerprints.\n\n    Args:\n        data_path (Path): Path to CSV file containing data.\n        smiles_col (str): Name of the column containing SMILES strings.\n        target_col (str): Name of the column containing target variable.\n\n    Returns:\n        tuple[pd.DataFrame, pd.DataFrame]: Cleaned dataframe and dataframe of Morgan\n                                           fingerprints.\n    \"\"\"</span>\n    <span class=\"n\">df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span><span class=\"n\">data_path</span><span class=\"p\">)</span>\n    <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">smiles_col</span><span class=\"p\">].</span><span class=\"nb\">apply</span><span class=\"p\">(</span><span class=\"k\">lambda</span> <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">Chem</span><span class=\"p\">.</span><span class=\"n\">MolFromSmiles</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n    <span class=\"n\">df</span><span class=\"p\">.</span><span class=\"n\">dropna</span><span class=\"p\">(</span><span class=\"n\">subset</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">],</span> <span class=\"n\">inplace</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"n\">mfpgen</span> <span class=\"o\">=</span> <span class=\"n\">rdFingerprintGenerator</span><span class=\"p\">.</span><span class=\"n\">GetMorganGenerator</span><span class=\"p\">(</span><span class=\"n\">radius</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"n\">fpSize</span><span class=\"o\">=</span><span class=\"mi\">2048</span><span class=\"p\">)</span>\n    <span class=\"n\">fps</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">mfpgen</span><span class=\"p\">.</span><span class=\"n\">GetFingerprint</span><span class=\"p\">(</span><span class=\"n\">mol</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">mol</span> <span class=\"ow\">in</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"s\">\"ROMol\"</span><span class=\"p\">]]</span>\n    <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">fps</span><span class=\"p\">))</span>\n    <span class=\"n\">df</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">[[</span><span class=\"n\">smiles_col</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">]]</span>\n    <span class=\"k\">return</span> <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">fps_df</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">save_data_for_chemprop</span><span class=\"p\">(</span>\n    <span class=\"n\">data</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">test_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Save training and test data for Chemprop CLI.\n\n    Args:\n        data (pd.DataFrame): Dataframe containing data.\n        project_name (str): Name of the project.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        test_idx (NDArray[np.int64]): List of indices for test data.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n    \"\"\"</span>\n    <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span>\n        <span class=\"n\">parents</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> <span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">].</span><span class=\"n\">to_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">].</span><span class=\"n\">to_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test.csv\"</span><span class=\"p\">,</span> <span class=\"n\">index</span><span class=\"o\">=</span><span class=\"bp\">False</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_chemprop_model</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Chemprop model using CLI.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        task_type (str): Type of task ('classification' or 'regression').\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"train\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--data-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--task-type\"</span><span class=\"p\">,</span>\n            <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--output-dir\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">make_chemprop_predictions</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Make predictions using trained Chemprop model.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"predict\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--model-paths\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--preds-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/preds.csv\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">dataset_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Calculate Chemprop fingerprints using trained Chemprop model.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        dataset_type (str): Type of dataset ('train' or 'test').\n    \"\"\"</span>\n    <span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span>\n        <span class=\"p\">[</span>\n            <span class=\"s\">\"chemprop\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"fingerprint\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--test-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">dataset_type</span><span class=\"si\">}</span><span class=\"s\">.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--output\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/</span><span class=\"si\">{</span><span class=\"n\">dataset_type</span><span class=\"si\">}</span><span class=\"s\">_fps.csv\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--model-path\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"models/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"--ffn-block-index\"</span><span class=\"p\">,</span>\n            <span class=\"s\">\"0\"</span><span class=\"p\">,</span>\n        <span class=\"p\">],</span>\n        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">stdout</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n        <span class=\"n\">stderr</span><span class=\"o\">=</span><span class=\"n\">subprocess</span><span class=\"p\">.</span><span class=\"n\">DEVNULL</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_morgan_sklearn_model</span><span class=\"p\">(</span>\n    <span class=\"n\">fps_df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">Series</span><span class=\"p\">,</span> <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span> <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Scikit-learn model using Morgan fingerprints.\n\n    Args:\n        fps_df (pd.DataFrame): Dataframe of Morgan fingerprints.\n        y (pd.Series): Series of target variable.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        BaseEstimator: Trained Scikit-learn model.\n    \"\"\"</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"n\">X_train</span> <span class=\"o\">=</span> <span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"n\">y_train</span> <span class=\"o\">=</span> <span class=\"n\">y</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"n\">rf</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">train_chemprop_sklearn_model</span><span class=\"p\">(</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">Series</span><span class=\"p\">,</span>\n    <span class=\"n\">train_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Train Scikit-learn model using Chemprop fingerprints.\n\n    Args:\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        y (pd.Series): Series of target variable.\n        train_idx (NDArray[np.int64]): List of indices for training data.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        BaseEstimator: Trained Scikit-learn model.\n    \"\"\"</span>\n    <span class=\"n\">X_train</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/train_fps_0.csv\"</span><span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestRegressor</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">rf</span> <span class=\"o\">=</span> <span class=\"n\">RandomForestClassifier</span><span class=\"p\">(</span><span class=\"n\">n_estimators</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n    <span class=\"n\">y_train</span> <span class=\"o\">=</span> <span class=\"n\">y</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">train_idx</span><span class=\"p\">]</span>\n    <span class=\"k\">return</span> <span class=\"n\">rf</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">gather_predictions</span><span class=\"p\">(</span>\n    <span class=\"n\">morgan_rf</span><span class=\"p\">:</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">,</span>\n    <span class=\"n\">chemprop_rf</span><span class=\"p\">:</span> <span class=\"n\">BaseEstimator</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">seed</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">fold_number</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span>\n    <span class=\"n\">df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">fps_df</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span>\n    <span class=\"n\">test_idx</span><span class=\"p\">:</span> <span class=\"n\">NDArray</span><span class=\"p\">[</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int64</span><span class=\"p\">],</span>\n    <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Gather predictions from all models.\n\n    Args:\n        morgan_rf (BaseEstimator): Trained Scikit-learn model on Morgan fingerprints.\n        chemprop_rf (BaseEstimator): Trained Scikit-learn model on Chemprop fingerprints.\n        project_name (str): Name of the project.\n        seed (int): Random seed used for splitting data.\n        fold_number (int): Fold number for cross-validation.\n        df (pd.DataFrame): Dataframe containing data.\n        fps_df (pd.DataFrame): Dataframe of Morgan fingerprints.\n        test_idx (NDArray[np.int64]): List of indices for test data.\n        target_col (str): Name of the column containing target variable.\n        task_type (str): Type of task ('classification' or 'regression').\n\n    Returns:\n        dict: Updated dictionary with predictions.\n    \"\"\"</span>\n    <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n    <span class=\"n\">rf_preds</span> <span class=\"o\">=</span> <span class=\"n\">morgan_rf</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">fps_df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">])</span>\n    <span class=\"n\">chemprop_rf_preds</span> <span class=\"o\">=</span> <span class=\"n\">chemprop_rf</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span>\n        <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/test_fps_0.csv\"</span>\n        <span class=\"p\">)</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">chemprop_probas</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">read_csv</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"data/</span><span class=\"si\">{</span><span class=\"n\">project_name</span><span class=\"si\">}</span><span class=\"s\">/seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">/fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">/preds.csv\"</span>\n    <span class=\"p\">)[</span><span class=\"n\">target_col</span><span class=\"p\">]</span>\n    <span class=\"k\">if</span> <span class=\"n\">task_type</span> <span class=\"o\">==</span> <span class=\"s\">\"regression\"</span><span class=\"p\">:</span>\n        <span class=\"n\">chemprop_preds</span> <span class=\"o\">=</span> <span class=\"n\">chemprop_probas</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">chemprop_preds</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">chemprop_probas</span> <span class=\"o\">&gt;=</span> <span class=\"mf\">0.5</span><span class=\"p\">).</span><span class=\"n\">astype</span><span class=\"p\">(</span><span class=\"nb\">int</span><span class=\"p\">)</span>\n    <span class=\"n\">y_true</span> <span class=\"o\">=</span> <span class=\"n\">df</span><span class=\"p\">.</span><span class=\"n\">iloc</span><span class=\"p\">[</span><span class=\"n\">test_idx</span><span class=\"p\">][</span><span class=\"n\">target_col</span><span class=\"p\">]</span>\n    <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"seed_</span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">_fold_</span><span class=\"si\">{</span><span class=\"n\">fold_number</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n        <span class=\"s\">\"rf\"</span><span class=\"p\">:</span> <span class=\"n\">rf_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"chemprop_rf\"</span><span class=\"p\">:</span> <span class=\"n\">chemprop_rf_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"chemprop\"</span><span class=\"p\">:</span> <span class=\"n\">chemprop_preds</span><span class=\"p\">,</span>\n        <span class=\"s\">\"true\"</span><span class=\"p\">:</span> <span class=\"n\">y_true</span><span class=\"p\">,</span>\n    <span class=\"p\">}</span>\n    <span class=\"k\">return</span> <span class=\"n\">predictions</span>\n</code></pre></div></div>\n<p>To compare the models, I used 5x5 cross-validation. This approach involves using five different random seeds to generate data splits for 5-fold cross-validation, resulting in 25 unique train-test splits. For each split, the models are trained on the training data and then used to make predictions on the test data.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">:</span> <span class=\"n\">Path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">5</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">5</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"p\">:</span> <span class=\"nb\">float</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run model comparison pipeline.\n\n    Args:\n        data_path (Path): Path to CSV file containing data.\n        project_name (str): Name of the project.\n        smiles_col (str): Name of the column containing SMILES strings.\n        target_col (str): Name of the column containing target variable.\n        task_type (str): Type of task ('classification' or 'regression').\n        n_seeds (int, optional): Number of random seeds for cross-validation. Defaults to 5.\n        n_folds (int, optional): Number of folds for cross-validation. Defaults to 5.\n        test_frac (float, optional): Fraction of data to use as test set. Defaults to 0.2.\n\n    Returns:\n        dict: Dictionary containing predictions from all models.\n    \"\"\"</span>\n    <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">fps_df</span> <span class=\"o\">=</span> <span class=\"n\">prepare_data</span><span class=\"p\">(</span><span class=\"n\">data_path</span><span class=\"p\">,</span> <span class=\"n\">smiles_col</span><span class=\"p\">,</span> <span class=\"n\">target_col</span><span class=\"p\">)</span>\n\n    <span class=\"n\">all_predictions</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n    <span class=\"k\">for</span> <span class=\"n\">seed</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">n_seeds</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span> <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"s\">\"Seeds\"</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">\"seed\"</span><span class=\"p\">):</span>\n        <span class=\"n\">cv</span> <span class=\"o\">=</span> <span class=\"n\">ShuffleSplit</span><span class=\"p\">(</span><span class=\"n\">n_splits</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"n\">seed</span><span class=\"p\">)</span>\n        <span class=\"k\">for</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">test_idx</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span>\n            <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">cv</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">df</span><span class=\"p\">)),</span>\n            <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n            <span class=\"n\">desc</span><span class=\"o\">=</span><span class=\"sa\">f</span><span class=\"s\">\"Seed </span><span class=\"si\">{</span><span class=\"n\">seed</span><span class=\"si\">}</span><span class=\"s\">: Folds\"</span><span class=\"p\">,</span>\n            <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">\"fold\"</span><span class=\"p\">,</span>\n        <span class=\"p\">):</span>\n            <span class=\"c1\"># Save data for training models using Chemprop CLI\n</span>            <span class=\"n\">save_data_for_chemprop</span><span class=\"p\">(</span>\n                <span class=\"n\">df</span><span class=\"p\">,</span> <span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">test_idx</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Chemprop model using CLI\n</span>            <span class=\"n\">train_chemprop_model</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"n\">task_type</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Make predictions on test set using trained Chemprop model\n</span>            <span class=\"n\">make_chemprop_predictions</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Use trained model to create fingerprints\n</span>            <span class=\"n\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"s\">\"train\"</span><span class=\"p\">)</span>\n            <span class=\"n\">calculate_chemprop_fingerprints</span><span class=\"p\">(</span><span class=\"n\">project_name</span><span class=\"p\">,</span> <span class=\"n\">seed</span><span class=\"p\">,</span> <span class=\"n\">fold_number</span><span class=\"p\">,</span> <span class=\"s\">\"test\"</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Scikit-learn models on Morgan fingerprints\n</span>            <span class=\"n\">morgan_rf</span> <span class=\"o\">=</span> <span class=\"n\">train_morgan_sklearn_model</span><span class=\"p\">(</span>\n                <span class=\"n\">fps_df</span><span class=\"p\">,</span> <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">target_col</span><span class=\"p\">],</span> <span class=\"n\">train_idx</span><span class=\"p\">,</span> <span class=\"n\">task_type</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Train Scikit-learn model on Chemprop fingerprints\n</span>            <span class=\"n\">chemprop_rf</span> <span class=\"o\">=</span> <span class=\"n\">train_chemprop_sklearn_model</span><span class=\"p\">(</span>\n                <span class=\"n\">project_name</span><span class=\"p\">,</span>\n                <span class=\"n\">seed</span><span class=\"p\">,</span>\n                <span class=\"n\">fold_number</span><span class=\"p\">,</span>\n                <span class=\"n\">df</span><span class=\"p\">[</span><span class=\"n\">target_col</span><span class=\"p\">],</span>\n                <span class=\"n\">train_idx</span><span class=\"p\">,</span>\n                <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n\n            <span class=\"c1\"># Gather predictions\n</span>            <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">gather_predictions</span><span class=\"p\">(</span>\n                <span class=\"n\">morgan_rf</span><span class=\"p\">,</span>\n                <span class=\"n\">chemprop_rf</span><span class=\"p\">,</span>\n                <span class=\"n\">project_name</span><span class=\"p\">,</span>\n                <span class=\"n\">seed</span><span class=\"p\">,</span>\n                <span class=\"n\">fold_number</span><span class=\"p\">,</span>\n                <span class=\"n\">df</span><span class=\"p\">,</span>\n                <span class=\"n\">fps_df</span><span class=\"p\">,</span>\n                <span class=\"n\">test_idx</span><span class=\"p\">,</span>\n                <span class=\"n\">target_col</span><span class=\"p\">,</span>\n                <span class=\"n\">task_type</span><span class=\"p\">,</span>\n            <span class=\"p\">)</span>\n            <span class=\"n\">all_predictions</span><span class=\"p\">.</span><span class=\"n\">update</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">all_predictions</span>\n</code></pre></div></div>\n<p>The next two cells run the training and prediction process for the classification and regression datasets.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Set up workspace\n</span><span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"models\"</span><span class=\"p\">).</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"n\">exist_ok</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">random</span><span class=\"p\">.</span><span class=\"n\">seed</span><span class=\"p\">(</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"n\">n_seeds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">n_folds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">test_frac</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span>\n<span class=\"n\">data_path</span> <span class=\"o\">=</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/raw/bsep.csv\"</span><span class=\"p\">)</span>\n<span class=\"n\">project_name</span> <span class=\"o\">=</span> <span class=\"s\">\"bsep\"</span>\n<span class=\"n\">smiles_col</span> <span class=\"o\">=</span> <span class=\"s\">\"smiles\"</span>\n<span class=\"n\">target_col</span> <span class=\"o\">=</span> <span class=\"s\">\"bsep\"</span>\n<span class=\"n\">task_type</span> <span class=\"o\">=</span> <span class=\"s\">\"classification\"</span>\n\n<span class=\"n\">classification_predictions</span> <span class=\"o\">=</span> <span class=\"n\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Seeds:   0%|          | 0/5 [00:00<?, ?seed/s]\nSeed 0: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 1: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 2: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 3: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 4: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\n</code></code></pre></div></div>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">n_seeds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">n_folds</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">test_frac</span> <span class=\"o\">=</span> <span class=\"mf\">0.2</span>\n<span class=\"n\">data_path</span> <span class=\"o\">=</span> <span class=\"n\">Path</span><span class=\"p\">(</span><span class=\"s\">\"data/raw/biogen_logS.csv\"</span><span class=\"p\">)</span>\n<span class=\"n\">project_name</span> <span class=\"o\">=</span> <span class=\"s\">\"logS\"</span>\n<span class=\"n\">smiles_col</span> <span class=\"o\">=</span> <span class=\"s\">\"SMILES\"</span>\n<span class=\"n\">target_col</span> <span class=\"o\">=</span> <span class=\"s\">\"logS\"</span>\n<span class=\"n\">task_type</span> <span class=\"o\">=</span> <span class=\"s\">\"regression\"</span>\n\n<span class=\"n\">regression_predictions</span> <span class=\"o\">=</span> <span class=\"n\">run_model_comparison</span><span class=\"p\">(</span>\n    <span class=\"n\">data_path</span><span class=\"p\">,</span>\n    <span class=\"n\">project_name</span><span class=\"p\">,</span>\n    <span class=\"n\">smiles_col</span><span class=\"p\">,</span>\n    <span class=\"n\">target_col</span><span class=\"p\">,</span>\n    <span class=\"n\">task_type</span><span class=\"p\">,</span>\n    <span class=\"n\">n_seeds</span><span class=\"o\">=</span><span class=\"n\">n_seeds</span><span class=\"p\">,</span>\n    <span class=\"n\">n_folds</span><span class=\"o\">=</span><span class=\"n\">n_folds</span><span class=\"p\">,</span>\n    <span class=\"n\">test_frac</span><span class=\"o\">=</span><span class=\"n\">test_frac</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Seeds:   0%|          | 0/5 [00:00<?, ?seed/s]\nSeed 0: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 1: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 2: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 3: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\nSeed 4: Folds:   0%|          | 0/5 [00:00<?, ?fold/s]\n</code></code></pre></div></div>\n<p>The next function takes the predicted values and true values from the test set and calculates a relevant performance metric.</p>\n<p>For the moderately imbalanced BSEP classification data (17% positive class), I used the Matthews Correlation Coefficient (MCC). MCC is a value between -1 and 1 and is a robust metric for imbalanced datasets. For the LogS regression data, I used the coefficient of determination (R2). An R2 value of 1 indicates perfect agreement between predicted and true values, with no lower limit for poor predictions.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">calculate_performance</span><span class=\"p\">(</span><span class=\"n\">preds_dict</span><span class=\"p\">:</span> <span class=\"nb\">dict</span><span class=\"p\">,</span> <span class=\"n\">metric</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Analyse performance of models.\n\n    Args:\n        preds_dict (dict): Dictionary containing predictions from all models.\n        metric (Callable): Metric function to evaluate performance.\n\n    Returns:\n        dict: Dictionary containing performance metrics for each model.\n    \"\"\"</span>\n    <span class=\"n\">metrics</span> <span class=\"o\">=</span> <span class=\"n\">defaultdict</span><span class=\"p\">(</span><span class=\"nb\">list</span><span class=\"p\">)</span>\n    <span class=\"k\">for</span> <span class=\"n\">predictions</span> <span class=\"ow\">in</span> <span class=\"n\">preds_dict</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">():</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"rf\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"chemprop_rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"chemprop_rf\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n        <span class=\"n\">metrics</span><span class=\"p\">[</span><span class=\"sa\">f</span><span class=\"s\">\"chemprop_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">].</span><span class=\"n\">append</span><span class=\"p\">(</span>\n            <span class=\"n\">metric</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"true\"</span><span class=\"p\">],</span> <span class=\"n\">predictions</span><span class=\"p\">[</span><span class=\"s\">\"chemprop\"</span><span class=\"p\">])</span>\n        <span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">metrics</span>\n\n\n<span class=\"n\">classification_metrics</span> <span class=\"o\">=</span> <span class=\"n\">calculate_performance</span><span class=\"p\">(</span>\n    <span class=\"n\">classification_predictions</span><span class=\"p\">,</span> <span class=\"n\">matthews_corrcoef</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">regression_metrics</span> <span class=\"o\">=</span> <span class=\"n\">calculate_performance</span><span class=\"p\">(</span><span class=\"n\">regression_predictions</span><span class=\"p\">,</span> <span class=\"n\">r2_score</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>To visualise and inspect the classification results, I've used confusion matricies. All three models have a significant number of True Negatives (TN) in the top left square. The Random Forest model and the Chemprop model have a significant number of False Negatives (FN), whereas the Chemprop-RF model has more True Positives (TP) and more False Positives (FP).</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">confusion_matricies</span><span class=\"p\">(</span><span class=\"n\">classification_predictions</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Confusion matrices for the Random Forest, Chemprop, and Chemprop-RF classification models\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-1.png\"/></p>\n<p>To visualise and inspect the regression results, I've used x-y scatter plots of the predicted values against the true values. The Random Forest regressor has poor predictions, with more values being overestimated. The Chemprop model and the Chemprop-RF models perform better, especially on these lower LogS values. Some performance metrics are annotated on the plots in the top left: R2, RMSE (root mean squared error), and MAE (mean absolute error).</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">scatter_plots</span><span class=\"p\">(</span><span class=\"n\">regression_predictions</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Predicted versus true value scatter plots for the Random Forest, Chemprop, and Chemprop-RF regression models, annotated with R2, RMSE, and MAE\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-2.png\"/></p>\n<p>As recommended in the paper I've linked, I used one-way ANOVA to compare the performance distributions from the 5x5 cross-validation and determine if there was a statistically significant difference between the means. This was followed by Tukey's Honest Significant Differences as a post-hoc pairwise test. The results are plotted using the Simultaneous Confidence Interval plot from the statsmodels Python library.</p>\n<p>For both the classification and regression datasets, the ANOVA results showed a statistically significant difference in model performance (p&lt;0.05).</p>\n<p>For the classification dataset, post-hoc tests revealed a statistically significant difference in performance between all pairs of models, with the Chemprop-RF model performing the best.</p>\n<p>For the regression dataset, the Random Forest model performed statistically significantly worse than the other two. However, there was no statistically significant difference between the performance of the Chemprop and Chemprop-RF models.</p>\n<p>The classification dataset (BSEP, 807 entries) is much smaller than the regression dataset (LogS, 2,173 entries). This might suggest that Chemprop-RF models are particularly effective for low-data problems, where there isn't enough data to train a high-performing Feed-Forward Neural Network (FFN) alone.</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># Adapted from https://github.com/PatWalters/practical_cheminformatics_posts/blob/main/adme_comparison/\n</span><span class=\"k\">def</span> <span class=\"nf\">run_anova</span><span class=\"p\">(</span><span class=\"n\">df_in</span><span class=\"p\">:</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">,</span> <span class=\"n\">col</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">float</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Run one-way ANOVA on model performance.\"\"\"</span>\n    <span class=\"n\">res_list</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n    <span class=\"k\">for</span> <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">v</span> <span class=\"ow\">in</span> <span class=\"n\">df_in</span><span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"method\"</span><span class=\"p\">):</span>\n        <span class=\"n\">res_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">v</span><span class=\"p\">[</span><span class=\"n\">col</span><span class=\"p\">].</span><span class=\"n\">values</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">f_oneway</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">res_list</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">metric_dict</span><span class=\"p\">:</span> <span class=\"nb\">dict</span><span class=\"p\">,</span> <span class=\"n\">metric</span><span class=\"p\">:</span> <span class=\"n\">Callable</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n    <span class=\"s\">\"\"\"Plot comparison of model performance using a specified metric.\n\n    Args:\n        metric_dict (dict): Dictionary containing performance metrics for each model.\n        metric (Callable): Metric function used to evaluate performance.\n    \"\"\"</span>\n    <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">ax</span> <span class=\"o\">=</span> <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">subplots</span><span class=\"p\">()</span>\n    <span class=\"n\">melt_df</span> <span class=\"o\">=</span> <span class=\"n\">pd</span><span class=\"p\">.</span><span class=\"n\">DataFrame</span><span class=\"p\">(</span><span class=\"n\">metric_dict</span><span class=\"p\">).</span><span class=\"n\">melt</span><span class=\"p\">()</span>\n    <span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"method\"</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">melt_df</span><span class=\"p\">.</span><span class=\"n\">variable</span><span class=\"p\">.</span><span class=\"nb\">map</span><span class=\"p\">(</span>\n        <span class=\"p\">{</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Random Forest\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"chemprop_rf_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Chemprop-RF\"</span><span class=\"p\">,</span>\n            <span class=\"sa\">f</span><span class=\"s\">\"chemprop_</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">:</span> <span class=\"s\">\"Chemprop\"</span><span class=\"p\">,</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">best_model</span> <span class=\"o\">=</span> <span class=\"p\">(</span>\n        <span class=\"n\">melt_df</span><span class=\"p\">.</span><span class=\"n\">groupby</span><span class=\"p\">(</span><span class=\"s\">\"method\"</span><span class=\"p\">)[</span><span class=\"s\">\"value\"</span><span class=\"p\">]</span>\n        <span class=\"p\">.</span><span class=\"n\">mean</span><span class=\"p\">()</span>\n        <span class=\"p\">.</span><span class=\"n\">reset_index</span><span class=\"p\">()</span>\n        <span class=\"p\">.</span><span class=\"n\">sort_values</span><span class=\"p\">(</span><span class=\"s\">\"value\"</span><span class=\"p\">,</span> <span class=\"n\">ascending</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n        <span class=\"p\">.</span><span class=\"n\">method</span><span class=\"p\">.</span><span class=\"n\">values</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">tukey</span> <span class=\"o\">=</span> <span class=\"n\">pairwise_tukeyhsd</span><span class=\"p\">(</span>\n        <span class=\"n\">endog</span><span class=\"o\">=</span><span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"value\"</span><span class=\"p\">],</span> <span class=\"n\">groups</span><span class=\"o\">=</span><span class=\"n\">melt_df</span><span class=\"p\">[</span><span class=\"s\">\"method\"</span><span class=\"p\">],</span> <span class=\"n\">alpha</span><span class=\"o\">=</span><span class=\"mf\">0.05</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">tukey</span><span class=\"p\">.</span><span class=\"n\">plot_simultaneous</span><span class=\"p\">(</span><span class=\"n\">comparison_name</span><span class=\"o\">=</span><span class=\"n\">best_model</span><span class=\"p\">,</span> <span class=\"n\">ax</span><span class=\"o\">=</span><span class=\"n\">ax</span><span class=\"p\">,</span> <span class=\"n\">figsize</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">8</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">))</span>\n    <span class=\"n\">anova_p_value</span> <span class=\"o\">=</span> <span class=\"n\">run_anova</span><span class=\"p\">(</span><span class=\"n\">melt_df</span><span class=\"p\">,</span> <span class=\"s\">\"value\"</span><span class=\"p\">)</span>\n    <span class=\"n\">ax</span><span class=\"p\">.</span><span class=\"n\">set_title</span><span class=\"p\">(</span>\n        <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">'_'</span><span class=\"p\">,</span> <span class=\"s\">' '</span><span class=\"p\">).</span><span class=\"n\">title</span><span class=\"p\">()</span><span class=\"si\">}</span><span class=\"s\"> \"</span> <span class=\"o\">+</span> <span class=\"sa\">f</span><span class=\"s\">\"ANOVA p=</span><span class=\"si\">{</span><span class=\"n\">anova_p_value</span><span class=\"si\">:</span><span class=\"p\">.</span><span class=\"mi\">3</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n    <span class=\"p\">)</span>\n    <span class=\"n\">ax</span><span class=\"p\">.</span><span class=\"n\">set_xlabel</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">metric</span><span class=\"p\">.</span><span class=\"n\">__name__</span><span class=\"p\">.</span><span class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s\">'_'</span><span class=\"p\">,</span> <span class=\"s\">' '</span><span class=\"p\">).</span><span class=\"n\">title</span><span class=\"p\">()</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n\n    <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">tight_layout</span><span class=\"p\">()</span>\n    <span class=\"n\">plt</span><span class=\"p\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n\n\n<span class=\"n\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">classification_metrics</span><span class=\"p\">,</span> <span class=\"n\">matthews_corrcoef</span><span class=\"p\">)</span>\n<span class=\"n\">plot_metric_comparison</span><span class=\"p\">(</span><span class=\"n\">regression_metrics</span><span class=\"p\">,</span> <span class=\"n\">r2_score</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p><img alt=\"Tukey HSD simultaneous confidence intervals comparing Matthews correlation coefficient across the three classification models, titled with the ANOVA p-value\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-3.png\"/></p>\n<p><img alt=\"Tukey HSD simultaneous confidence intervals comparing R2 across the three regression models, titled with the ANOVA p-value\" src=\"https://jonswain.github.io/images/chemprop-rf/chemprop-rf-4.png\"/></p>","doi":"https://doi.org/10.59350/gsppn-fg196","guid":"https://jonswain.github.io/chemprop-rf","language":"en","license":"https://creativecommons.org/licenses/by/4.0/legalcode","published_at":1757635200,"rid":"68c9f-7tb53","summary":"Whilst neural networks ( <strong> NNs </strong> ) have done amazing things with unstructured data such as text and images, they've traditionally been outperformed on tabular data by Gradient-Boosted Decision Trees ( <strong> GBDTs </strong> ), although recent advances such as TabPFN and TabICL suggest that the performance gap may have closed. I've written a bit about TabPFN for chemical datasets here.","tags":["Ai","Cheminformatics","Data-science","Machine-learning"],"title":"Chemprop-RF: A Hybrid Approach to Chemical Property Prediction","updated_at":1788763518,"url":"https://jonswain.github.io/chemprop-rf-hybrid-chemical-property-prediction/","version":"v1"}],"out_of":55729,"page":1,"per_page":10,"total-results":55729}
