In this article, we are looking at performing calculations between readouts of a single protocol and calculations with chemical properties. Go to this article for calculations that involve multiple protocols.
Introduction
Calculations are based on existing assay data within your CDD Vault protocols, as well as chemical properties of the registered compounds, and numeric constants. In this manner, you can build dynamic mathematical functions that use protocol readouts as variables, and are automatically updated when new raw data is imported into the Vault.
Calculations are defined as new readouts in existing protocols, based on previously defined readouts. In a new protocol, calculations will be added after the initial set of raw data readout definitions.
We assume here that you are familiar with defining protocols and readouts already, and if you are new to protocol creation, please start here.
Basic steps to add a calculated readout
On the "Protocol Details" tab, and click "Create a new readout definition".
In the readout definition pop up, set the Data Type to 'Calculated':
You will need to provide a name for the readout definition (that does not conflict with any other readout names in the same protocol), and then fill in the formula in the provided formula builder.
Begin typing the following characters in the Formula box to see what kinds of functions can be applied and combined:
- Type a left square bracket [ to bring up a list of available readout definitions.
- Type a curly bracket { to reveal a list of chemical properties.
- Auto-complete and syntax help will appear as you type.
- Use parentheses ( ) to indicate the order of operations.
Reference variables
Calculations performed on referenced variables are dynamic: when the underlying variables are changed, the calculation is updated automatically. In this way, a single calculation can be defined to be performed on previously imported data, and on any incoming data.
There are two kinds of variables: readout definitions and chemical/biological properties.
Readout definitions - []
For your calculation, you can reference other existing readout definitions from the current protocol. Make sure to define all of the basic readout definitions before you start to combine them using calculations. For example, if you plan to build a calculation that includes % Inhibition values, % Inhibition needs to be defined ahead of the calculation, otherwise it will not be available as a reference variable when you start to build your formula.
Syntax
Type in an open square bracket, then click a readout name in the drop-down list: the matching bracket will be inserted automatically. The list of available options includes all available readout definitions with your current protocol appearing first, followed by all other protocols in alphabetical order.
[Protocol name -> readout definition name (units)]
e.g. [Primary Screen -> Raw Fluorescence (RFU)]
Scope
By default, when using the above syntax, calculations are performed for each molecule/batch with data in the referenced readout in per readout row mode.
- If the referenced readout is blank for a given batch, no calculated value will be returned for this batch.
- If there are multiple values in the referenced readout for a given batch (replicate data), then multiple values will be returned by the calculation.
- If the calculation references two readout definitions, a calculated value will be returned only if both of the referenced readout values occur on the same row. This happens when the data are added to the protocol in a single import event, and are both on the same row of the import file.
Here's an example of taking the ratio of two readouts for a single batch: the ratio value is calculated only when both CFP (477 nm) and YFP (514 nm) values appear on the same row, even though there are individual data points for both readouts on two other rows.

To perform a calculation based on multiple readout rows per batch, such as the scenario shown in the screen-shot above, the referenced readout(s) should aggregated with Average or Median functions.
Chemical and biological properties - {}
When you import molecule and macromolecule structures into CDD Vault, chemical and biological properties are automatically calculated, and displayed on the molecule details page, or in the search results. These properties may be referenced as variables within calculated readout definitions.
Syntax
Type in an open curly bracket, then click a chemical property name in the drop-down list: the matching bracket will be inserted automatically.
{property name} e.g. {Molecular Weight}
Here's the list of available chemical properties:
- Molecular weight
- log P
- Number of aromatic rings
- H-bond donors
- H-bond acceptors
- Lipinski violations
- log D
- log S
- pKa
- CNS MPO score
- BBB score
- Topological polar surface area
- Exact mass
- Fsp3
- Heavy atom count
- Rotatable bonds
- Nucleotide sequence length
- GC-content
- ATU-content
- Nucleotide sequence molecular weight
- Melting temperature
- Amino acid sequence length
- Hydrophobicity
- Hydrophilicity
- Amino acid sequence molecular weight
- Monoisotopic molecular weight
- Isoelectric point
- GRAVY
- Formula weight
Scope
When a chemical or biological property is referenced in a calculated readout, molecule level properties are used, with the exception of formula weight, which is calculated per batch of molecule.
Functions
The following functions can be performed on referenced readout definitions. These functions may be used as part of another calculation, and in fact should be used on replicate data from multiple runs, or imported from multiple files.
Average - average() or mean()
Average returns an average of all readout values in the selected aggregation scope for the chosen readout definition. When performed as a standalone calculation (not part of a larger formula), the returned result also includes the standard deviation, and the number (n) of values.
average([Protocol name -> readout definition name (units)])
e.g. [Primary Screen -> Inhibition (%)]
output: Avg ± StDev (n)
23.43 ± 3.16 (n=8)
Unless explicitly requested (see Strip modifiers - finite_boundary()), a bounded value (that contains "less than" or "greater than" numeric modifier) will not be included when computing the average if there is another measurement that is consistent with it and is more specific. Exact values are always included.
Less specific bounded values are not included:
- Average(<1, <10) = <1
- Average(20, 30, <100) = 25
- Average(80, <100, 150) = 115 (<100 is consistent with 80 and less specific)
Bounded values are retained if they are not consistent with an exact value:
- Average(<2, 8) = <5
This rule is based on how drug discovery data is normally generated. For example, if you perform a concentration-response assay between 100 to 10,000 nM, and the IC50 comes out as <100 nM, you will test at lower concentrations, say between 1 and 200 nM. If the IC50 in the second test was 80 nM, it makes sense to leave out the <100 nM from the first assay, because it has been superseded by the more specific 80 nM measurement.
On the other hand, if you ran the 100 to 10,000 nM assay twice and the only IC50s you had were <100 and 120 nM, we shouldn’t ignore the <100 nM, because doing so would result in a number that was artificially high.
Geomean - geomean()
Geomean returns a geometric average of all readout values in the selected aggregation scope of the chosen readout definition. When performed as a standalone calculation (not part of a larger formula), the returned result also includes the standard deviation, and the number (n) of values.
Geomean is automatically inserted when averaging CDD-computed EC50 values. To over-ride the geomean aggregation, use "mean" in the formula.
geomean([Protocol name -> readout definition name (units) (CDD)])
e.g. geomean([Secondary Screen ->IC50 (uM)(CDD)])
output: Avg ×/÷ StDev (n)
23.43 ×/÷ 3.16 (n=8)
A bounded value (that contains "less than" or "greater than" numeric modifier) will not be included when computing the average if there is another measurement that is consistent with it and is more specific (see Strip modifiers - finite_boundary()). Exact values are instead always included. See the section above - Average - for examples.
Median - median()
Median returns the median of all readout values in the selected aggregation scope for the chosen readout definition. When performed as a standalone calculation (not part of a larger formula), the returned result also includes the standard deviation, and the number (n) of values.
median([Protocol name -> readout definition name (units) (CDD)])
e.g. median([Secondary Screen ->IC50 (uM)(CDD)])
output: Median (n)
23.43 (n=8)
Sum - sum()
Sum function adds all readout values in the selected aggregation scope of the chosen readout definition. When performed as a standalone calculation (not part of a larger formula), the returned result also includes the number (n) of values.
sum([Protocol name -> readout definition name (units)])
e.g. sum([Inventory -> Amount Debited (mg)])
output: Sum (n)
187.04 (n=8)
Strip modifiers - finite_boundary()
This function removes the modifiers (> or <) from a measured intercept value, returning the numeric value (or blank if no value is measured).
finite_boundary([Protocol name -> PlotName : Intercept -> Intercept (unit)])
e.g. finite_boundary([DR assay -> PlotName : IC50 -> IC50 (uM)])
For example, the function will return the following outputs:
- 10 - if the intercept (e.g. IC50) is >10
- 0.01 - if the intercept (e.g. IC50) is <0.01
- 5 - if the intercept (e.g. IC50) is 5
It can be used as a part of a conditional statement (see Conditional statement - if(condition, value_if_true, value_if_false)), or on its own.
This function can only be used with intercepts originating from a CDD Vault Plot readout definition.
Conditional statement - if(condition, value_if_true, value_if_false)
Checks whether a condition is true or false and returns the corresponding value. A condition can compare values, or check whether a value is available at all.
It supports comparison operators (>, >=, <, <=, <>, =), as well as logical operators (and, or, xor, not) which resolve to a boolean (true/false) within the conditional statement.
if(condition, value if true, value if false)
e.g. if([DR assay -> PlotName : IC50 -> IC50 (uM)]>20,
500, [DR assay -> PlotName : IC50 -> IC50 (uM)])
This example will output 500 if the IC50 is above 20, otherwise it will return the measured intercept.
It supports nested statements, which when combined with intercept_calculated() allows to check first whether a value is present, and just then perform a further check and the desired operation.
Successful fitting check - intercept_calculated()
Checks whether an intercept was successfully calculated upon fitting a Hill, biphasic, or bell equation, and returns a Boolean value. It can be used only within a conditional statement.
if(intercept_calculated([Protocol name -> PlotName : Intercept -> Intercept (unit)]),
value if intercept was calculated, value if intercept calculation failed)
e.g. if(intercept_calculated([DR assay -> PlotName : IC50 -> IC50 (uM)]),
finite_boundary([DR assay -> PlotName : IC50 -> IC50 (uM)]), 0)
The above example will return the IC50 stripped of modifiers if the dose-response data were successfully fitted, otherwise it will return 0.
Operators
Mathematical operations can be performed on constant numbers, readouts, including calculated readouts and chemical properties using the following operators
| * | multiplication |
|---|---|
| / | division |
| + | addition |
| - | subtraction |
| ^ | power : base^exponent |
| log() | logarithm base 10 : log(number) |
| ln() | natural logarithm, base e : ln(number) |
| exp() | e to the power of number : exp(number) |
Calculations using conditions
This is a more advanced topic - please be sure to have read the following two articles to understand protocol conditions and calculation syntax: article on cross protocol calculations as well as setting up conditions.
In brief - if any of your readouts are dependent on a condition, you may choose to integrate the condition into a calculated readout. Please note, that the more conditions you have defined in your protocol, the more complex the formulas may be become. This can become of importance if you change conditions well after protocol creation and having imported data to this protocol. Nevertheless, referencing to a condition within a calculated readout is of advantage for cross-protocol calculations.
As long as you are calculating only within a single protocol without cross-referencing, you don't necessarily have to specify the condition in the calculations. This has the additional advantage that you only require one calculated readout, instead of one for each condition. Though by all means you may want to define separate columns nevertheless - for sake of clarity and, more importantly for the possibility of using them in cross-protocol calculations.
For example: A "Cytotox Assay" having two conditions (Hela, 3D7) may use this simple formula:
geomean([Cytotox Assay -> IC50 (uM) (CDD)])
In the search results table this results in two columns for the mean IC50, one for each condition, just as if you had defined these two separate calculations:
geomean([Cytotox Assay -> IC50 (uM) (CDD)]<[Cells]="HeLa">)
geomean([Cytotox Assay -> IC50 (uM) (CDD)]<[Cells]="3D7">)
Example calculations
pIC50
The following calculation is based on uM values, hence the additional factor 10E-6).
-log([Secondary Screen -> IC50 (uM)(CDD)] * 10^-6)
Geometric mean of the IC50
If you don't use pIC50s (you really should), then IC50 values can be averaged using the geometric mean, since they are based on a logarithmic scale of concentrations. When you average the CDD-calculated IC50, the formula will automatically default to the geometric mean.
geomean([Secondary Screen -> IC50 (uM)(CDD)])
output: Avg ×/÷ StDev (n)
23.43 ×/÷ 3.16 (n=8)
Absolute IC50
Use the calculated fit parameters from the Dose-Response calculation, and substitute the absolute inhibition value. We show the equation for 50% activity, but a similar calculation can be done at any other activity. Thus, the formula will be applicable to other IC## or EC## calculations as well.
[Protocol Name-> EC50 (uM)] / ((([Protocol Name-> Maximum Response (%)] - [Protocol Name-> Baseline Response (%)]) / (50 - [Protocol Name-> Baseline Response (%)])-1)^(1/[Protocol Name-> Hill Slope]))
Fluorescence polarization ratio
([Primary Screen -> nFparallel] - [Primary Screen -> Fperpendicular]) / [Primary Screen -> Fparallel] + [Primary Screen -> Fperpendicular]
Binding Efficiency Index
BEI is defined as (pIC50/molecular weight (kDa)). Based on the calculated IC50 from a protocol and molecular weight, reported in CDD in g/mol. Don't forget to convert IC50 to Molar units, and to convert molecular weight to KiloDaltons.
average([Secondary Screen -> pIC50]) / ({Molecular Weight} * 10^-3)
Lipophilic Efficiency
Lipophilic Efficiency is defined as (pIC50-cLogP). You will find clogP as part of the calculated properties: The logarithm of the partition coefficient. The implementation uses the approach described in Gedeck et al. (DOI: 10.1021/acs.jcim.7b00315) using publicly available log P data. (See more.)
average([Secondary Screen -> pIC50]) - {log P}
Compare fit and data if fit is successful
In the following example, a nested conditional statement is used to first check whether the fit was successful, and then if the maximum fitted value is higher than 50% of the maximum measured value.
if(intercept_calculated([DR assay -> PlotName : IC50 -> IC50 (uM)]), if(and(finite_boundary([DR assay -> PlotName : IC50 -> IC50 (uM)]) <> [DR assay -> PlotName : IC50 -> IC50 (uM)], [DR assay -> PlotName : IC50 -> Maximum response]< 1.5*[DR assay -> PlotName : IC50 -> Maximum measured]), [DR assay -> PlotName : IC50 -> Maximum response], [DR assay -> PlotName : IC50 -> Maximum measured]), [DR assay -> PlotName : IC50 -> Maximum measured])
The first condition is necessary because the comparison of a fitted value vs data cannot be computed for unsuccessful fitting.
The second condition checks both if the fitted curve has a maximum response <1.5x the maximum measured value and that the intercept isn't out of range, as well as the desired comparison, using the and() logical operator.
Set pIC50 to 0 for out of range IC50s and inactive compounds
This nested conditional calculation requires the existence of a pre-calculated pIC50, but this can be also calculated in line.
if(intercept_calculated([DR assay -> PlotName : IC50 -> IC50 (uM)]), if(finite_boundary([DR assay -> PlotName : IC50 -> IC50 (uM)]) <> [DR assay -> PlotName : IC50 -> IC50 (uM)]), 0, [DR assay -> pIC50 ]), 0)
The first condition is necessary because the finite_boundary() function cannot be computed for unsuccessful fitting.
The second condition checks whether the IC50 is out of range by comparing the modifier-stripped IC50 vs the original IC50.