IC50 or pIC50?

Where we talk about scaling

small-molecules
drug discovery
Informatics
Python
py50
Author

Tony E. Lin

Published

January 18, 2024

Is IC50 the Only Way?

Sometimes it can feel like science is spoken in an unknowable language. Reading figures you see symbols like ƅ or unit scales for the metric system that make no sense (Does ā€œKangaroos Hop Down Mountains Drinking Chocolate Milkā€ even work?!?!?!), especially when starting out. For communicating your work, how the data and the units are represented matters.

That brings me back to IC50. I know, I have mentioned it a lot (see here and here). In short, IC50 value indicates if a drug can inhibit a protein activity by 50%. It is an important indicator of how potent a drug may be. However, obtaining the IC50 value can vary in so many ways. The drug potency can also be an indication of what stage the drug is in during development pipeline. The end result makes reporting a given drugā€™s potency muddy, especially when trying to compare potency between compounds or compare structures during the development stages.

This can be seen when comparing drugs in the dummy table below:

# Dummy Data
df
Drug IC50 (ĀµM)
0 Drug 1 30.000000
1 Drug 2 60.000000
2 Drug 3 0.000345
3 Drug 4 0.099000
4 Drug 5 0.800000
5 Drug 6 0.940000

Ew.

All reported drugs are in the same units, microMolar (ĀµM), but with varying results. Is Drug 1 50% more potent than Drug 2? Are Drug 5 and Drug 6 differ by 0.14 ĀµM. Does that mean they are equally potent? Reporting results in this way has several issues: - A lot of digits! - Difference in 50% implies 50% increase in potency - Implies linear scale

The last point is the most important. Rarely are dose-response curves in the linear scale (though it can happen at times). Instead, dose-response curves are more commonly in logarithmic scale. Drugs can have a broad range of concentrations and a logarithmic scale better captures this for plotting. The logarithmic scale also allow curves to be plotted in a nice sigmoidal curve, which can automatically guide our eyes to the all important 50% response value on the curve.

So there must be a better way, right?

pIC50 - A scale That Makes Sense

Enter pIC50. This is essentially the negative log of the IC50 in molar concentration:

\[pIC50 = -log_{10}(IC50)\]

This has the advantage of scaling results similar to other logarithmic scales, like the pH scale. This is a handy, because it allows us to quickly gauge a drugs potency. For example:

  • IC50 of 1 ĀµM is 10-6 M = pIC50 of 6.0
  • IC50 of 100 nM is 10-7 M, which is pIC50 = 7.0
  • IC50 of 10 nM is 10-8 M, which is pIC50 = 8.0
  • IC50 of 1 nM is 10-9 M = pIC50 of 9.0

Hey, pIC50 is basically the negative exponent!

The pIC50 scales the results in a more reasonable manner. An IC50 of 100 nM is 10-7 is equal to a pIC50 of 7.0. Likewise, an IC50 of 50 nM is 5 x10-8 M, which is also 10-7.3 M, which is pIC50 = 7.3

Another way of thinking of the pIC50 scale is comparing it to another logarithmic scale - the pH scale. Remember that between each number, from 3 to 4, it is a difference of 10. So a pH of 3 is ten times more acidic than a pH of 4.

This works the same way for pIC50. However, the pIC50 scale has the added benefit of moving in one direction, where the higher number represents greater potency.

This can make a big difference in the reporting of a drug. Remember the first table above? We can quickly convert those values into the pIC50 scale.

# Because units are already in ĀµM, must convert log accordingly
df['pIC50'] = -np.log10(df['IC50 (ĀµM)']* 1e-6).round(2)
df
Drug IC50 (ĀµM) pIC50
0 Drug 1 30.000000 4.52
1 Drug 2 60.000000 4.22
2 Drug 3 0.000345 9.46
3 Drug 4 0.099000 7.00
4 Drug 5 0.800000 6.10
5 Drug 6 0.940000 6.03

Now that makes the drugs in the table make more sense! The drugs may vary wildly by units, but as pIC50s they are scaled in a manner that can make more intuitive sense. Using the pIC50 of 6.0 (or IC50 of 1,000 nM) as a cutoff, Drug 4, Drug 5, and Drug 6 are the only ones that appear potent. On the pI50 scale, Drug 1 and Drug 2 do not look as appealing.

A Real-World Example

Another example of how pIC50 can be used is demoed here through the development of Capivasertib (AZD5363). Capivasertib is an AKT kinase inhibitor recently approved in 2023. It is the 7th approved fragment-derived drug. It was built around a 7-azaindole core and originally started with an IC50 > 100 ĀµM for AKT. That high IC50 is typical starting point for fragment-based drug design. Looking at the first report of Capivasertib, published in 2013, we can list the intermediate compoundā€™s and their reported IC50. From there we can scale the results to pIC50 and get a better idea of the compounds potency as it moved through the drug optimization stages.

NOTE I did not follow the optimization linearly - the IC50 varied between compounds, with some more or less potent depending on the moiety substituted and explored. From the paper, we see that the chemists were able to generate many molecules with very high potency, however issues of selectivity were of great concern, leading to a lot of tweaking. A lot of work was put into the development of Capivasertib (not just potency but selectivity!) and should not be dismissed by my simple demo.

demo_df
Compound IC50 (ĀµM) pIC50
0 7-azaindole 100.000 4.00
1 Compound 33 0.276 6.56
2 Compound 41 1.313 5.88
3 Compound 53 0.030 7.52
4 Capivasertib 0.003 8.52

The IC50 (ĀµM) of the reported intermediates in the published article are, objectively, not good to look at. Especially in Table 3 as the IC50 jumped from 9 nM to almost 2,800 nM depending on the substituent. The above table shows how scaling the IC50 to pIC50 can make the information more intuitive. As the molecule becomes more potent, it will have a higher pIC50 number. Thus, there is a need to forever improve the compound to ā€œachieveā€ a higher ranking on the pIC50 scale. For Capivarsertibā€™s case, it has a pIC50 of 8.52 for AKT1.

šŸ”ŒPlugs: py50 Can Scale to pIC50!

To aid in data representation, py50 comes equipped with a function to scale the IC50 into pIC50. This is performed using the calculate_pic50() function. It is built on top of the calculate_absolute_ic50() function. Thus both the relative and absolute pIC50 will be reported. Again, what you report depends on your dataset. Use your best judgement!

An example of how this works can be seen below:

calculation = calc_data.calculate_pic50(name_col='Compound Name', concentration_col='Compound Conc', response_col='% Inhibition Avg')
# To keep IC50 columns
calculation = calculation[['compound_name', 'relative ic50 (nM)', 'absolute ic50 (nM)', 'relative pIC50', 'absolute pIC50']]

calculation.round(2)
compound_name relative ic50 (nM) absolute ic50 (nM) relative pIC50 absolute pIC50
0 Drug 1 429.96 579.62 6.37 6.24
1 Drug 2 423.05 660.69 6.37 6.18
2 Drug 3 644.38 921.71 6.19 6.04
3 Drug 4 398.60 422.45 6.40 6.37

Now when using py50 to calculate IC50 values, anyone can quickly scale their results to pIC50. Hopefully this will bring a more convenient way to represent and display data to classmates, coworkers, advisors, etc.

For anyone wanting to look further, the full notebook for this post can be found here. A good post was has been written up by CDD Vault here