
Chris Parmer
November 7, 2026
Dose-Response Curves - A Complete Guide
TL;DR
- A dose-response curve plots how strongly something responds (a cell, a plant, a patient) against how much of a drug or chemical it received, with the dose on a logarithmic axis, so the response usually traces an S-shaped (sigmoid) curve between two plateaus.
- Dose-response curves are also known as concentration-response curves, dose-response relationships, and exposure-response curves, and when they are fitted with the standard model, four-parameter logistic (4PL) curves. They are similar to scatter plots with a fitted line, and usually carry error bars for the replicates at each dose.
- The 4PL, or Hill, model gives four numbers: the bottom and top plateaus, the EC50 (the half-maximal effective concentration, or IC50 for an inhibitor), which measures potency, and the Hill slope, which says how steep the middle is.
- Pharmacologists use the curve to rank drug candidates by potency, clinical teams to choose the dose for a phase 3 trial, toxicologists to set safe exposure levels, and labs to read unknown samples off an immunoassay standard curve.
- You can make a dose-response curve with Plotly in Python by fitting the 4PL with scipy.optimize.curve_fit and drawing the replicate means with error bars and the fitted curve on a log x axis (type="log"). A short helper function is below.
What is a dose-response curve?
Dose-response curves look like this:

Data: Tox21 via PubChem BioAssay AID 743080, the estrogen receptor alpha antagonist screen in BG1 cells run by the US National Center for Advancing Translational Sciences (NCATS).
How do you read a dose-response curve? In the example above, notice these things:
- The x axis is the concentration of fulvestrant, a breast cancer drug, on a log scale: each gridline is ten times the one before, from 1 nanomolar (nM) to 100 micromolar (µM). The 15 concentrations are mostly 2.2-fold apart, so they fall almost evenly across the axis.
- The y axis is the signal of the estrogen receptor in cells stimulated with estradiol, as a percentage of the signal with estradiol alone. Each dot is the mean of three wells, and its error bar is ± 1 standard deviation (SD) of those wells.
- The line is a four-parameter logistic fit. Its top plateau is 103%, its bottom plateau 0%, so fulvestrant blocks the receptor’s signal completely at high concentrations.
- The open circle is the IC50, the half-maximal inhibitory concentration: 20.4 nM, with a 95% confidence interval of 17.5 to 23.8 nM (the black bar on the axis). Half the signal is gone at that concentration, and a drug with a lower IC50 is more potent.
- The Hill slope of -1.38 says how steep the fall is. It is negative because the curve falls; at a slope of 1, the response goes from 10% to 90% of its range over an 81-fold range of doses, and at 1.38 it takes about 24-fold.
It’s often used for charts like these:

Choosing the dose in a clinical trial. In a phase 2 trial of daily semaglutide, an Emax model puts half the maximum drug effect at 0.14 mg and 0.4 mg at 74% of it, which is the kind of curve dose-finding studies use to pick the dose for phase 3. Data: ClinicalTrials.gov NCT02453711, O’Neil et al. (2018), The Lancet.

Immunoassay standard curves. An enzyme-linked immunosorbent assay (ELISA) reads unknown samples off a 4PL curve of known standards, and in 11 runs of a DNase ELISA the same optical density of 1.0 reads as 2.71 to 3.24, a 20% spread, which is why every plate gets its own standard curve. Data: R dataset DNase, from Davidian and Giltinan (1995), Nonlinear Models for Repeated Measurement Data.

Toxicology and plant science bioassays. The dataset the R package drc starts its documentation with: ferulic acid halves the root growth of ryegrass at 3.06 mM, and the six untreated controls sit in their own panel because a dose of zero has no place on a log axis. Data: Inderjit, Streibig, and Olofsdotter (2002), Physiologia Plantarum, via the R package drc.
A short history
The S-shaped curve comes from physiology. In 1910 A. V. Hill described how hemoglobin takes up oxygen with the equation that now carries his name (Hill 1910), and in 1926 A. J. Clark showed that the response of frog muscle to acetylcholine followed the same form when plotted against the log of the concentration (Clark 1926). J. W. Trevan argued in 1927 that the dose that affects half of a group is a more reliable measure than the smallest dose that has an effect, the idea behind the ED50 and its relatives (Trevan 1927), and Chester Bliss’s probit method (1934) gave a way to fit curves to the share of a group that responds (Bliss 1934). R. P. Stephenson introduced efficacy in 1956, the size of the response a drug can produce, as separate from how strongly it binds (Stephenson 1956), and DeLean, Munson, and Rodbard showed in 1978 how to fit and compare families of four-parameter logistic curves for bioassays, radioligand assays, and physiological dose-response studies (DeLean, Munson, and Rodbard 1978). Today the R package drc (Ritz et al. 2015), GraphPad Prism, and NCATS’s quantitative high-throughput screening (qHTS) pipeline (Inglese et al. 2006) fit these curves by the thousand, and regulators ask for dose-response information in drug submissions: the ICH E4 guideline of the International Council for Harmonisation (ICH), Dose-Response Information to Support Drug Registration (1994), and the US Food and Drug Administration’s exposure-response guidance.
How a dose-response curve works
A dose-response curve looks like a scatter plot with a line through it, but each part follows a convention from pharmacology. This section explains them one at a time, with the fulvestrant data where real data shows the point best and with curves drawn from the equation where it doesn’t.
Why the dose axis is logarithmic
Doses that matter span many orders of magnitude, and the body responds to ratios of dose more than to differences. On a linear axis the fulvestrant data, from 0.6 nM to 70 µM, crush against the left edge; on a log axis each doubling of the dose gets the same room, and the response becomes a symmetric S:

Data: Tox21 via PubChem BioAssay AID 743080.
Experiments are designed for this axis too: concentrations are usually a dilution series with a fixed ratio, such as 2-, 3-, or 10-fold steps, so they land evenly on it. GraphPad Prism asks you to enter the log of the concentration, and pharmacology papers often label the axis as the log of the molar concentration, with ticks at -9, -8, and -7 (GraphPad). Labeling the ticks with the concentrations themselves (1 nM, 10 nM) on a log axis shows the same thing and is easier to read.
The four parameters of the 4PL (Hill) equation
The standard model is the four-parameter logistic, also called the Hill equation or, in GraphPad Prism, “log(agonist) vs. response, variable slope”. Each parameter is one feature you can point to on the curve:

Data: an illustrative curve drawn from the equation, not data.
The EC50 (half-maximal effective concentration) is the dose that gives a response halfway between the bottom and the top, which is not always a response of 50% (GraphPad): the 4-hydroxytamoxifen curve in example 1 below has a top of 118%, so its halfway point is at 62%. For an inhibitor it is called the IC50 (half-maximal inhibitory concentration), for a dose given to a whole animal or person the ED50 (median effective dose), and in the US National Cancer Institute’s NCI-60 cancer cell screen the GI50 (50% growth inhibition). Medicinal chemists often report pEC50 or pIC50, the negative log10 of the molar value, so an IC50 of 20 nM is a pIC50 of 7.7, following the terms of the International Union of Basic and Clinical Pharmacology (IUPHAR) (Neubig et al. 2003).
The Hill slope
The Hill slope, or Hill coefficient, sets how steep the middle of the curve is. At a slope of 1 the response goes from 10% to 90% of its range over an 81-fold range of doses; at 2 it takes 9-fold, and at 0.5 it takes 6,561-fold:

Data: illustrative curves drawn from the equation, all with EC50 = 1; not data.
A slope near 1 is what simple one-to-one binding of a drug to its receptor predicts. Much steeper curves can mean cooperative binding, as in Hill’s hemoglobin, or a threshold effect such as cells dying, and very shallow ones can mean a mix of targets or a compound coming out of solution. Inhibitors get negative slopes in GraphPad’s convention, and drc reports the same curve with the sign flipped, so check which convention a paper uses before comparing slopes.
Potency and efficacy
Two drugs can differ in two ways, and a dose-response curve separates them. Potency is how much drug it takes, read from the EC50: a more potent drug’s curve sits further left. Efficacy is how big the effect can get, read from the top plateau: a partial agonist turns the receptor on, but its curve flattens below a full agonist’s however much you add:

Data: illustrative curves drawn from the equation, not data.
For treatment, efficacy often matters more than potency, since a less potent drug can be given at a higher dose, while no dose of a less efficacious drug makes up the difference. Example 1 below shows potency on real data: four drugs that all block the estrogen receptor completely, at IC50s from 3.4 nM to 12.2 µM.
Agonists and antagonists
An agonist turns a receptor on, so its curve rises with the dose. An antagonist blocks the receptor, so it is tested against an agonist that is already present, and its curve falls. The Tox21 program screens about 10,000 chemicals both ways on the estrogen receptor, which is how these two curves come from the same cells:

Data: Tox21 via PubChem BioAssay AID 743079 (agonist mode) and AID 743080 (antagonist mode).
Bisphenol A, a chemical used in some plastics, activates the receptor with an EC50 of 0.52 µM, which is how screening programs flag possible endocrine disruptors. Its signal falls again at the two highest concentrations, so those points are left out of the fit and shown as open circles. This is a common choice when a curve turns down for a reason the model doesn’t describe, such as cells starting to die, and it should be shown on the chart, not done silently.
Error bars and confidence bands
A dose-response chart usually carries two kinds of uncertainty, and they answer different questions. Error bars describe the replicates at each dose, and a confidence band describes the fitted curve:

Data: Inderjit, Streibig, and Olofsdotter (2002), via the R package drc (ryegrass).
The standard deviation (SD) shows how much single wells or plants vary, and the standard error of the mean (SEM), the SD divided by the square root of the number of replicates, shows how precisely the mean is known, so it shrinks as you add replicates. Both are common, so say which one you drew. The confidence band comes from the fit’s covariance matrix (the delta method, which drc uses) and is narrowest near the EC50, where the data pin the curve down. The EC50’s own interval is computed on the log scale, because that is the parameter the fit estimates, which is why it is asymmetric around the EC50 in concentration units. Our error bars guide covers the choice in more detail.
Controls at zero dose
A dose of zero is minus infinity on a log axis, so the untreated controls can’t be plotted there. The convention, which the drc package draws with plot(..., broken = TRUE) and the ryegrass and semaglutide charts above use, is a broken axis: the controls at the left, separated by a break mark, and the doses on the log axis to the right. The fit can still use the controls, because the 4PL approaches its top or bottom plateau as the dose goes to zero.
Alternatives to a dose-response curve
Here’s the same four estrogen receptor antagonists drawn six ways. Some are better for the question, some worse, some just different:

Data: Tox21 via PubChem BioAssay AID 743080.
- Line chart or scatter on a linear axis (worse). Three of the four curves collapse into a vertical line at the left, and the difference in potency, the whole point, disappears.
- Grouped bar chart (worse). One bar per drug per concentration makes 60 bars that are hard to compare, and the bars treat concentrations as categories, so uneven steps look even.
- IC50 dot plot with confidence intervals (better for comparing many compounds). Once the curves are fitted, one dot per compound on a log axis ranks potency at a glance and scales to hundreds of compounds, like a forest plot. It hides the shape, so a partial or bell-shaped curve looks the same as a clean one.
- Heatmap of compounds by concentration (different). This is how screening teams look at thousands of compounds at once; the shift of the color edge shows potency, but reading a number off it is hard.
- Hill plot (different). Plotting log(response / (100 − response)) against log dose turns a 4PL into a straight line whose slope is the Hill coefficient. It was the way to estimate slopes before computers fitted curves, and it falls apart near the plateaus, as the noisy ends show.
The dose-response curve itself, a scatter plot of the replicates with a fitted line on a log axis, is the one chart that shows potency, efficacy, slope, and the data’s scatter together, which is why papers and regulators expect it.
How to make a dose-response curve
With AI
ChatGPT, Claude Code, GitHub Copilot, Cursor, and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, and say which model to fit, which error bars to draw, and how to handle the controls:
Make a plotly dose-response curve from fulvestrant.csv (columns: conc_um, signal; 15 concentrations x 3 wells).- Fit a four-parameter logistic with scipy.optimize.curve_fit:y = bottom + (top - bottom) / (1 + 10 ** ((log_ec50 - log10(x)) * hill)).- Plot the mean of each concentration with +/- 1 SD error bars, the fitted curve and its 95% confidence band(delta method from pcov), on a log x axis labeled in nM and µM.- Mark the IC50 with an open circle and label it with its 95% CI, computed on the log scale.- Check: print the four parameters and the IC50 with its interval.
With Plotly Studio
Plotly Studio is a desktop app where you point an AI at a dataset and it builds charts and a Dash app around your questions. Ask for a dose-response curve the same way:
Show dose-response curves of signal against concentration on a log axis for each compound, with mean ± SD error bars, a four-parameter logistic fit per compound, the IC50 of each in a table, and a dropdown to pick the compounds.
Make a dose-response curve in Python or JavaScript
This is the code AI tools write under the hood. You can make a dose-response curve with Plotly from a go.Scatter of the means with error_y, a second go.Scatter line of the fitted model, and a log x axis. This helper fits the 4PL with SciPy and returns the figure, and every example in this guide builds on the same idea:
import numpy as npimport pandas as pdimport plotly.graph_objects as gofrom scipy.optimize import curve_fitdef logistic4(x, bottom, top, log_ec50, hill):"""Four-parameter logistic (Hill) curve, in the form GraphPad Prism uses."""return bottom + (top - bottom) / (1 + 10 ** ((log_ec50 - np.log10(x)) * hill))def dose_response(conc, resp, name="", color="#2a78d6"):"""Means with SD error bars on a log axis and the fitted 4PL curve. Returns the figure and the fit."""df = pd.DataFrame({"conc": conc, "resp": resp})m = df.groupby("conc").resp.agg(["mean", "std"]).reset_index()rising = m["mean"].iloc[-1] > m["mean"].iloc[0]p0 = [m["mean"].min(), m["mean"].max(), np.log10(m.conc).median(), 1.0 if rising else -1.0]popt, pcov = curve_fit(logistic4, df.conc, df.resp, p0=p0, maxfev=20000)xg = np.logspace(np.log10(m.conc.min()) - 0.3, np.log10(m.conc.max()) + 0.3, 300)ec50 = 10 ** popt[2]fig = go.Figure([go.Scatter(x=m.conc, y=m["mean"], error_y=dict(array=m["std"]), mode="markers", name=name,marker=dict(size=10, color=color)),go.Scatter(x=xg, y=logistic4(xg, *popt), mode="lines", name="4PL fit", line_color=color),go.Scatter(x=[ec50], y=[logistic4(ec50, *popt)], mode="markers+text", name=f"EC50 {ec50:.3g}",text=[f"EC50 {ec50:.3g}"], textposition="middle right", marker=dict(size=12, color="black")),])fig.update_xaxes(type="log", title="Concentration (µM, log scale)")fig.update_layout(template="simple_white", yaxis_title="Response (%)")return fig, popt, pcovdf = pd.read_csv("fulvestrant.csv") # conc_um, signal: 15 concentrations x 3 wellsfig, popt, pcov = dose_response(df.conc_um, df.signal, name="Fulvestrant")se = np.sqrt(np.diag(pcov))print(f"IC50 {1000 * 10 ** popt[2]:.1f} nM, Hill slope {popt[3]:.2f}, log IC50 ± SE {popt[2]:.3f} ± {se[2]:.3f}")fig.show()
On the fulvestrant data it prints an IC50 of 20.4 nM and a Hill slope of -1.38. For the confidence band, evaluate the curve’s gradient with respect to the four parameters at each x, combine it with pcov (the delta method), and draw the upper and lower bounds as one fill="toself" shape; the R package drc computes the same band for you. One thing to watch: shapes and annotations on a log axis take log10 positions, so put an annotation at x=np.log10(ec50), or draw markers as a trace as above.
In JavaScript, plotly.js draws the same thing from the means and the fitted parameters:
<div id="chart"></div><script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script><script>// Fulvestrant in the Tox21 estrogen receptor antagonist assay (PubChem AID 743080): mean and SD of 3 wellsconst conc = [0.0005955, 0.001231, 0.002749, 0.006139, 0.01371, 0.03063, 0.06841, 0.1529,0.3413, 0.7623, 1.703, 3.794, 8.468, 32.48, 70.35]; // µMconst mean = [103.4, 99.9, 95.7, 87.7, 64.5, 37.1, 17.3, 4.0, 1.9, -0.4, 0.4, 0.1, -0.7, -1.3, 1.7];const sd = [5.3, 6.6, 5.9, 2.6, 16.0, 10.8, 1.9, 2.2, 3.4, 2.3, 2.6, 2.1, 2.6, 3.4, 3.8];// 4PL parameters from the fit in Python: bottom, top plateau, log10 IC50 (µM), Hill slopeconst [bottom, plateau, logIC50, hill] = [-0.29, 102.94, -1.690, -1.381];const f = x => bottom + (plateau - bottom) / (1 + Math.pow(10, (logIC50 - Math.log10(x)) * hill));const xs = Array.from({length: 300}, (_, i) => Math.pow(10, -3.5 + i * 5.5 / 299));const ic50 = Math.pow(10, logIC50);Plotly.newPlot("chart", [{x: conc, y: mean, mode: "markers", name: "Fulvestrant", error_y: {type: "data", array: sd},marker: {size: 10, color: "#2a78d6"}},{x: xs, y: xs.map(f), mode: "lines", name: "4PL fit", line: {color: "#2a78d6"}},{x: [ic50], y: [f(ic50)], mode: "markers+text", text: [`IC50 ${(ic50 * 1000).toFixed(1)} nM`],textposition: "middle right", marker: {size: 12, color: "black"}, showlegend: false}], {width: 900, height: 500,xaxis: {type: "log", title: {text: "Fulvestrant (µM, log scale)"}},yaxis: {title: {text: "Signal, % of estradiol alone"}, ticksuffix: "%"}});</script>
Share it
Every Plotly chart has a Share chart button (the cloud icon in its toolbar) that uploads it to Plotly Cloud and gives you a link. Only you can see it until you change its visibility.
For a live app, put the chart in a Dash app and publish it with the Plotly Cloud CLI. Coding agents can run these commands for you:
pip install "dash[cloud]"plotly user loginplotly app publish
If your data has to stay in your own cloud or data center, publish to Dash Enterprise instead.
Dos and don’ts
Most of the work in a dose-response chart is putting the doses on the right axis and showing the fit and its uncertainty. Here is the fulvestrant data both ways, in the same blue.
Don’t plot the concentrations as equally spaced categories with raw labels like 0.0005955, which hides that the step from 8.47 to 32.5 µM is 3.8-fold while the others are 2.2-fold, join the means with straight lines instead of fitting a curve, leave out the error bars, or keep raw column names and a serif font:

Do use a log concentration axis labeled in nM and µM, show the replicate means with error bars and say what they are, draw the fitted curve with its confidence band, and mark the IC50 with its interval:

Checklist
Do
- Put the dose on a log axis, and label the ticks with concentrations and units.
- Space the doses by a fixed ratio, and cover both plateaus, so the top and bottom are measured rather than guessed.
- Show the replicates or their mean with error bars, and say whether the bars are SD, SEM, or a confidence interval.
- Report the EC50 or IC50 with a confidence interval computed on the log scale, and the Hill slope.
- Plot zero-dose controls in a separate panel on a broken axis.
- Compare compounds on one axis in the same assay, and normalize each run to its own controls.
Don’t
- Read the EC50 off where the curve crosses 50% when the plateaus aren’t 0% and 100%.
- Extrapolate an EC50 beyond the doses you tested; if the curve never reaches a plateau, report “greater than” the top dose.
- Drop points that don’t fit without saying so on the chart.
- Fit a 4PL to a curve that rises and then falls; use a model for that shape, as in the hormesis example.
Options to customize
A dose-response curve in Plotly is a go.Scatter of the data plus a go.Scatter of the fit on a log axis, so these are the options that matter most. The full lists are in the scatter trace reference, the error bars documentation and the log axes documentation.
- fig.update_xaxes(type="log"): the log dose axis; set range in log10 units, for example range=[-3.5, 2] for 0.3 nM to 100 µM.
- tickvals and ticktext: label the decades as 1 nM, 10 nM, 100 nM instead of 0.001, 0.01, 0.1.
- error_y=dict(type="data", array=sd): error bars from the replicates; add arrayminus for asymmetric intervals such as binomial ones.
- go.Scatter(fill="toself"): a confidence band, drawn as one closed shape from the upper bound and the reversed lower bound.
- mode="markers" for the data and mode="lines" for the fit, so the data isn’t joined dot to dot.
- make_subplots(cols=2, shared_yaxes=True, column_widths=[0.1, 0.9]): a broken axis with the zero-dose controls in their own panel.
- add_hline and add_annotation: plateaus, the 50% level, and the EC50 label; on a log axis give annotation positions in log10 units.
- hovertemplate: the concentration, mean, and number of replicates on hover.
- go.Scattergl: hundreds or thousands of curves on one chart, as in a screening overview.
More dose-response curve examples
Six more dose-response curves built from real public data, each with its source, the technique that makes it work, and a prompt to rebuild it. Five use datasets from the R package drc, which is how most courses teach dose-response analysis.
1. Four estrogen receptor drugs, from 3 nM to 12 µM

All four drugs block the estrogen receptor completely, so they differ in potency, not efficacy. In this assay, 4-hydroxytamoxifen, the form tamoxifen is turned into in the body, needs 226 times less drug than tamoxifen to block half the signal. Data: Tox21 via PubChem BioAssay AID 743080.
Tip: label each curve directly with its IC50, which reads faster than a legend when the curves are side by side.
Get the data from PubChem BioAssay AID 743080 (Tox21 estrogen receptor alpha antagonist screen in BG1 cells,per-well responses from the PUG REST doseresponse endpoint) for fulvestrant, raloxifene, 4-hydroxytamoxifen andtamoxifen, and shape it into a table (columns: compound, conc_um, response_pct).Build plotly dose-response curves of 100 + response_pct on a log axis, mean ± SD per concentration, one 4PL fitand color per drug, IC50s as open circles, each curve labeled with its name and IC50.
2. Relative potency of two herbicides

When two curves share their plateaus, the ratio of their ED50s is a single relative potency. Bentazone needs 2.34 times less product than glyphosate (95% CI 1.90 to 2.87) to halve the growth of white mustard. Data: Christensen, Teicher, and Streibig (2003), Pest Management Science, via the R package drc (S.alba).
Tip: fit the curves jointly with shared top and bottom, so the potency ratio gets one confidence interval.
Get the data from the R package drc's S.alba dataset (Christensen, Teicher, and Streibig 2003, dry matter of whitemustard in g per pot after glyphosate or bentazone at 0 to 640 g/ha, 4 pots per dose) and shape it into a table(columns: herbicide, dose_g_ha, dry_matter_g).Build plotly dose-response curves with a joint 4PL fit that shares bottom and top across herbicides, a brokenaxis for the untreated pots, mean ± SD, both ED50s marked and the relative potency with its 95% CI in the subtitle.
3. Water fleas at 24 and 48 hours: a quantal response

Here the response is the share of 20 water fleas that can no longer swim, a quantal (yes or no) response, fitted with a binomial logistic regression. A day longer in the water lowers the EC50 3.4-fold, from 5,134 to 1,509, which is why the OECD 202 acute immobilisation test reads the animals at both 24 and 48 hours. Data: Nina Cedergreen, University of Copenhagen, via the R package drc (daphnids); test guideline OECD Test No. 202.
Tip: draw binomial confidence intervals (Wilson) as asymmetric error bars with arrayminus, so they stay between 0% and 100%.
Get the data from the R package drc's daphnids dataset (Nina Cedergreen, University of Copenhagen; immobileDaphnia magna out of 20 at 8 concentrations, counted at 24 h and 48 h) and shape it into a table (columns:dose, immobile, total, time).Build a plotly dose-response chart of the share immobilized on a log axis, one binomial logistic regression onlog10 dose per time, Wilson 95% intervals as asymmetric error bars, and each EC50 labeled with its 95% CI.
4. Hormesis: lettuce grows more at low doses

Low doses of isobutyl alcohol raised lettuce growth by up to 22% before higher doses cut it, a pattern called hormesis. A 4PL can only rise or fall, so it misses the peak; the Brain-Cousens model adds a term for the low-dose rise. Data: van Ewijk and Hoekstra (1993), Ecotoxicology and Environmental Safety, via the R package drc (lettuce).
Tip: draw the control level as a reference line, so the rise above it is visible without reading the axis.
Get the data from the R package drc's lettuce dataset (van Ewijk and Hoekstra 1993, shoot biomass of lettuce ing after 21 days in isobutyl alcohol at 0 to 100 mg/l, 2 plants per concentration) and shape it into a table(columns: conc_mg_l, biomass_g).Build a plotly dose-response chart with a Brain-Cousens hormesis fit, a dashed 4PL for comparison, a broken axisfor the controls, a dotted line at the fitted control level and the peak labeled.
5. The baroreflex: an asymmetric curve on a linear axis

As blood pressure rises past 75 mmHg, a rabbit’s heart slows from 351 to 150 beats a minute. The fall bends more sharply at the bottom than at the top, so a five-parameter logistic fits with a residual sum of squares of 668 against 1,843 for the symmetric 4PL. Data: Ricketts and Head (1999), American Journal of Physiology, via the R package drc (heartrate).
Tip: use a linear axis when the “dose” is a physiological variable spread over a narrow range, such as pressure from 50 to 90 mmHg.
Get the data from the R package drc's heartrate dataset (Ricketts and Head 1999, 18 readings of mean arterialpressure in mmHg and heart rate in beats per minute from a rabbit baroreflex study) and shape it into a table(columns: pressure_mmhg, heart_rate_bpm).Build a plotly chart with a five-parameter (baro5) logistic fit and a dashed 4PL for comparison on a linear xaxis, the plateaus labeled and both residual sums of squares in the subtitle.
6. One assay on six days: normalize to each day’s control

The controls of six runs of the same reporter assay differ 2.8-fold, from 1,003 to 2,830, so the raw curves don’t line up. As a percentage of each day’s control they overlap, and a pooled fit gives an IC50 of 0.07 µM (95% CI 0.04 to 0.13) for the fungicide vinclozolin. Data: Nellemann et al. (2003), Toxicological Sciences, via the R package drc (vinclozolin).
Tip: show the raw and normalized runs side by side once, so readers can trust the normalization.
Get the data from the R package drc's vinclozolin dataset (Nellemann et al. 2003, luminescence of an androgenreceptor reporter assay in Chinese hamster ovary cells at 8 vinclozolin concentrations and a control, 6experiments) and shape it into a table (columns: experiment, conc_um, luminescence).Build two plotly panels: raw luminescence by day, and each day as a percentage of its own control with a pooledlogistic fit (top fixed at 100%) and the IC50 with its 95% CI.