API reference¶
Every public name in depictr is documented here, grouped along the path an analysis takes:
look at the data, reduce it or follow it over time, read the model, check the model, judge a
classifier, set the look, and compose and save the result. The groups follow the
R package's reference index in the same
order, so a function can be looked up in the same place on either site.
The two indexes are cut slightly differently in the few places where the Python package's own families do not line up with the R one. Time series has no group of its own here, and those functions close the 'Multivariate, survival and time series' group, which is where the Python package has always documented them. The R index's 'Diagnostics and classification' is split in two, because the classification curves are mostly computed by scikit-learn and reached through a separate install extra. R's 'Uncertainty and power' has no counterpart either, and the posterior and power curves are documented with the other model estimates they are drawn from. What the R index calls 'Theming, accessibility and reporting' appears here as 'Theme, palette and accessibility', covering the theme, the colour-vision checks, the figure audit and the label helper, while plot composition and saving move to the last group beside the datasets and the one-figure model report stays with the diagnostics it collects.
Everything listed under a group heading is importable straight from depictr. The same plots are
shown rendered, with the code that produced them, in the gallery.
Exploratory analysis¶
A first look at a dataset: distributions, categories, relationships, correlation, cumulative distributions, outliers and a missing-data map.
These plots are shown, with the code that produced them, on the exploring data page of the gallery.
explore_distribution ¶
explore_distribution(data, x, group=None, kind='density', bins=30, alpha=None, legend_inside=False, title=None)
Plot the distribution of a numeric variable, optionally split by a group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column to display. Any other column, a boolean one
included, raises |
required |
group
|
str
|
A grouping column mapped to colour/fill. A numeric code counts as a
group, but a numeric column with more than 20 distinct values raises
|
None
|
kind
|
('density', 'histogram', 'both')
|
What to draw. Any other value raises |
"density"
|
bins
|
int
|
Number of histogram bins. |
30
|
alpha
|
float
|
Fill transparency, useful when groups overlap. |
None
|
legend_inside
|
bool
|
When |
False
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.explore_distribution(ld, "RT", group="condition", kind="both")
Source code in src/depictr/eda.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
explore_categorical ¶
explore_categorical(data, x, group=None, proportion=True, title=None)
Bar chart of a categorical variable, optionally grouped (dodged).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
x
|
str
|
The categorical column. A numeric column is accepted only when it has
at most 20 distinct values, such as a 0/1 coding or a Likert item. A
continuous one would be drawn as one bar per value, so it raises
|
required |
group
|
str
|
A second categorical column mapped to fill, drawn side by side. A
numeric code counts as a group, but a numeric column with more than 20
distinct values raises |
None
|
proportion
|
bool
|
Show proportions within each group rather than raw counts. |
True
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.explore_categorical(wb, "education", group="region")
Source code in src/depictr/eda.py
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
explore_bivariate ¶
explore_bivariate(data, x, y, title=None)
Plot the relationship between two columns, choosing the plot from their types.
The plot is selected from the column dtypes:
- two numeric columns give a scatter plot with a trend line
(:func:
scatter_trend); - one numeric and one categorical column give boxplots of the numeric variable across the categories, with the raw points jittered over them;
- two categorical columns give a tile of joint counts, shaded by frequency.
Datetime and timedelta columns count as numeric, so against a numeric
column they give a scatter plot with a trend, and against a categorical one
boxplots of their values across the categories. A boolean column counts as
categorical, as a logical vector does in the R twin. A duration is drawn as a
number in the unit :func:summary_table reports it in, named in the axis
title, as in stay (days). The R twin draws a difftime the same way,
in its own units.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Names of the two columns to relate. |
required |
y
|
str
|
Names of the two columns to relate. |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.explore_bivariate(ld, "condition", "RT")
Source code in src/depictr/bivariate.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
scatter_trend ¶
scatter_trend(data, x, y, group=None, method='lm', title=None)
Scatter plot with a fitted trend line, and a confidence band where the smoother gives one.
The band appears only when the smoother computes one, as "lm",
"glm", "gls" and "mavg" do, and "loess" once scikit-misc is
installed (plotnine raises an error for it otherwise). "lowess" and
"rlm" draw the line alone, and plotnine warns that it has no interval
for them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Names of the numeric columns for the horizontal and vertical axes. |
required |
y
|
str
|
Names of the numeric columns for the horizontal and vertical axes. |
required |
group
|
str
|
A grouping column. When given, points and a separate trend line are
coloured by group in the depictr palette. A numeric code counts as a
group, but a numeric column with more than 20 distinct values raises
|
None
|
method
|
str
|
Smoothing method passed to :func: |
'lm'
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> cy = dp.crop_yield()
>>> p = dp.scatter_trend(cy, "fertiliser", "yield", group="treatment")
Source code in src/depictr/bivariate.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
correlation_heatmap ¶
correlation_heatmap(data, cols=None, method='pearson', show_values=True, digits=2, title=None)
Heatmap of pairwise correlations among numeric columns.
Each cell can be labelled with its correlation, drawn in black or white, whichever has the higher contrast against the cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
cols
|
list of str
|
Columns to include; defaults to all numeric columns. A named column
that is not numeric or boolean, such as text or a datetime, raises
|
None
|
method
|
('pearson', 'spearman', 'kendall')
|
The correlation coefficient, computed by
:meth: |
"pearson"
|
show_values
|
bool
|
Whether to label each cell with its correlation. |
True
|
digits
|
int
|
The number of decimal places in the labels. |
2
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
Zero-variance columns are dropped with a warning, and any correlation
that remains undefined is labelled |
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.correlation_heatmap(wb)
>>> q = dp.correlation_heatmap(wb, method="spearman", show_values=False)
Source code in src/depictr/eda.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | |
missingness_map ¶
missingness_map(data, sort=True, legend_inside=False, title=None)
Tile map of missing values, one column per variable and one row per record.
Variables are ordered most- to least-missing, so the worst offenders sit on the left. The percentage missing is shown in each axis label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
sort
|
bool
|
Order columns by their proportion of missing values. |
True
|
legend_inside
|
bool
|
When |
False
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.missingness_map(wb, legend_inside=True)
Source code in src/depictr/eda.py
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | |
ecdf_plot ¶
ecdf_plot(data, x, group=None, legend_inside=False, title=None)
Empirical cumulative distribution function, one step curve per group.
The ECDF reads off the proportion of observations at or below each value, so it shows the whole distribution without the smoothing choices a density makes. Curves that sit to the right are shifted towards larger values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column. |
required |
group
|
str
|
A grouping column mapped to colour, drawn as one curve per level. A
numeric code counts as a group, but a numeric column with more than 20
distinct values raises |
None
|
legend_inside
|
bool
|
When |
False
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.ecdf_plot(ld, "RT", group="condition", legend_inside=True)
Source code in src/depictr/distributions_extra.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
ridgeline_plot ¶
ridgeline_plot(data, x, group, title=None)
Overlapping densities, one ridge per group level.
Each level gets its own filled density on a shared x-axis, stacked so the ridges overlap a little. Reading top to bottom gives a quick sense of how the distribution shifts across levels. Levels are ordered by their median so the progression is easy to follow, and each keeps the colour of its place in the group's own order, as in the R twin.
The overlap is faked with :func:plotnine.facet_grid: one narrow row per
level, panel spacing pulled negative so neighbouring ridges touch. It stays a
single ggplot, since plotnine has no dedicated ridgeline geom.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column. |
required |
group
|
str
|
The grouping column; one ridge is drawn per level. A numeric code
counts as a group, but a numeric column with more than 20 distinct
values raises |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.ridgeline_plot(wb, "life_satisfaction", "region")
Source code in src/depictr/distributions_extra.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
dumbbell_plot ¶
dumbbell_plot(data, category, value, group, legend_inside=False, title=None)
Dumbbell plot pairing two group values per category.
For a group with exactly two levels, each category becomes one row with a point for each level joined by a segment. The length of the segment is the gap between the two levels, which is the comparison the plot is built to show.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Either one row per category-and-group combination, or raw rows that are averaged to one value per combination. |
required |
category
|
str
|
The categorical column, one row per level on the y-axis. |
required |
value
|
str
|
The numeric column plotted along the x-axis. |
required |
group
|
str
|
A two-level grouping column; its levels are the two points. They are taken in a categorical's declared order, and otherwise sorted, as in the R twin. The first level is the start of each segment, orders the categories and takes the first colour of the palette. |
required |
legend_inside
|
bool
|
When |
False
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> wb = dp.wellbeing_survey()
>>> wb = wb.assign(age_group=np.where(wb["age"] < 50, "under 50", "50 or over"))
>>> p = dp.dumbbell_plot(wb, "region", "life_satisfaction", "age_group")
Source code in src/depictr/distributions_extra.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | |
outlier_plot ¶
outlier_plot(data, x, title=None)
Box plot of one variable with outliers flagged in the accent colour.
Points beyond 1.5 times the interquartile range from the nearest quartile, the usual Tukey rule the box plot's whiskers already use, are drawn on top in the accent colour. The box itself suppresses its own outlier markers so they are not drawn twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column to inspect. |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> cy = dp.crop_yield()
>>> p = dp.outlier_plot(cy, "yield")
Source code in src/depictr/distributions_extra.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | |
group_comparison_plot ¶
group_comparison_plot(data, x, group, title=None, conf_level=0.95)
Group means with confidence intervals over the raw points.
The estimate-with-uncertainty alternative to a bar chart: each group shows its raw observations (jittered), the mean, and a confidence interval for the mean. Showing the spread of the data alongside the estimate guards against reading too much into a difference in means.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column being compared. |
required |
group
|
str
|
The grouping column on the x-axis. A numeric code counts as a group,
but a numeric column with more than 20 distinct values raises
|
required |
title
|
str
|
Plot title. |
None
|
conf_level
|
float
|
Confidence level for the t intervals, a single number strictly between 0 and 1. |
0.95
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Notes
Each interval is the t interval for the group's mean, as in the R twin and
in the upper panel of :func:depictr.estimation_plot. Its half-width is the
standard error times the 1 - (1 - conf_level) / 2 quantile of the t
distribution with n - 1 degrees of freedom. It describes the precision
of the mean, not the spread of the data. depictr 0.3.0 used 1.96 standard
errors, a normal approximation that covers only 81% of the time at n = 3
and 88% at n = 5 for a nominal 95%.
A group with a single observation has no degrees of freedom for the interval, so only its mean is drawn and a warning names the group.
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.group_comparison_plot(ld, "RT", "condition")
Source code in src/depictr/distributions_extra.py
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | |
raincloud_plot ¶
raincloud_plot(data, x, group=None, title=None)
Raincloud plot of a numeric variable, optionally split by a group.
A raincloud sets three views of the same distribution side by side: a half-violin density (the "cloud"), a narrow boxplot for the median and quartiles, and the jittered raw points (the "rain"). Seeing the shape, the summary and every observation together guards against the boxplot hiding bimodality or a thin tail (Allen et al., 2021).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
x
|
str
|
Name of the numeric column whose distribution is shown. |
required |
group
|
str
|
A categorical column. Each level gets its own raincloud along the
horizontal axis, coloured by the depictr palette. With no group a single
raincloud is drawn. A numeric code counts as a group, but a numeric
column with more than 20 distinct values raises |
None
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Allen, M., Poggiali, D., Whitaker, K., Marshall, T. R., van Langen, J., & Kievit, R. A. (2021). Raincloud plots: A multi-platform tool for robust data visualization. Wellcome Open Research, 4, 63. https://doi.org/10.12688/wellcomeopenres.15191.2
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.raincloud_plot(ld, "RT", group="condition")
Source code in src/depictr/eda_extra.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
explore_pairs ¶
explore_pairs(data, cols=None, title=None)
Scatter-plot matrix over a few numeric columns.
Builds the familiar pairs grid: a scatter of every variable against every other off the diagonal, and that variable's own density on the diagonal. Each cell is a small themed plot, composed into an N x N grid. The number of columns is capped at five, beyond which the cells are too small to read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
cols
|
list of str
|
Numeric columns to include, in order. Defaults to the first few numeric
columns of |
None
|
title
|
str
|
Accepted for API symmetry, but dropped with a warning: plotnine compositions cannot carry a figure-level title, so the matrix draws without one. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot or Compose
|
The composed grid from :func: |
Examples:
>>> import depictr as dp
>>> cy = dp.crop_yield()
>>> p = dp.explore_pairs(cy, cols=["rainfall", "fertiliser", "yield"])
Source code in src/depictr/eda_extra.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
Multivariate, survival and time series¶
Principal components and clustering, Kaplan-Meier survival, and the time-series family (series, autocorrelation, decomposition and seasonal views).
These plots are shown, with the code that produced them, on the multivariate and time series page of the gallery, and the survival curve on the classification and survival page.
pca_plot ¶
pca_plot(data, cols=None, group=None, title=None)
PCA biplot: observations on the first two components, with loading arrows.
Observations are scored on PC1 and PC2 and shown as points, optionally
coloured by group. Each original variable is drawn as an arrow from the
origin along its loading, labelled with the variable name just beyond the
arrow head. The axis labels report the percentage of variance each component
explains.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
cols
|
list of str
|
Numeric columns to decompose; defaults to all numeric columns. |
None
|
group
|
str
|
A column mapped to the colour of the observation points. Kept aside from
the decomposition, which uses only the numeric |
None
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Notes
Each arrow is a variable's loadings on the two components, multiplied by a
factor that all the arrows share, as in the R twin. The factor makes the
largest coordinate of any arrow three quarters of the largest absolute
score. Both axes use that one factor, as base R's biplot() uses one
ratio for both, and the aspect ratio is fixed at 1. An arrow's angle on the
page is therefore its angle in loading space, whatever the size of the
figure. The panel's height and width follow the ranges of its two axes, so
a figure of another shape leaves blank space around the panel. Because the
scores are drawn as they are and the arrows as loadings, the angle between
two arrows does not estimate the correlation between their variables.
Arrows and labels are drawn in a neutral dark grey (#333333), which is
not in the palette, so no arrow reads as part of a group.
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.pca_plot(wb, group="region")
Source code in src/depictr/multivariate.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
scree_plot ¶
scree_plot(data, cols=None, title=None)
Scree plot: variance explained per component, with the cumulative line.
Bars give the proportion of variance each principal component explains; an overlaid line and points give the running cumulative proportion. Both share the (0, 1) y-axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
cols
|
list of str
|
Numeric columns to decompose; defaults to all numeric columns. |
None
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.scree_plot(wb)
Source code in src/depictr/multivariate.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | |
cluster_plot ¶
cluster_plot(data, cols=None, k=3, title=None, clusters=None)
k-means clusters drawn on the first two principal components.
k-means is run on the standardised data in all of its dimensions, with the
settings :func:silhouette_plot uses, so the two functions show the same
partition for the same k, as the R twin does. The principal components
serve only to display it. Points are drawn at their PC1 and PC2 scores,
coloured by cluster, and each centroid is marked by a larger outlined
point at the projection of its k-means centre. The axis labels report the
percentage of variance each component explains.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
cols
|
list of str
|
Numeric columns to use; defaults to all numeric columns. |
None
|
k
|
int
|
Number of clusters. Ignored when |
3
|
title
|
str
|
|
None
|
clusters
|
array - like
|
A clustering computed elsewhere, such as the labels of a hierarchical
or PAM clustering. When it is given, it is drawn and k-means is not
run. It needs one entry per row of |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
The cluster of each complete-case row is attached as |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
The two displayed components hold only part of the variance, so k-means run on them would partition a different space from the one the silhouette widths describe. A k-means centroid is the cluster's mean in the full space, projected, which equals the mean of its members' scores. Supplied clusters are marked at the mean of their members' scores.
k-means settles in a local optimum. scikit-learn's algorithm and random
starts differ from those of R's kmeans(), so on data without clear
clusters the two twins can still assign some points differently for the
same k.
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.cluster_plot(wb, k=3)
>>> p.clusters.value_counts().sort_index().index.tolist()
[1, 2, 3]
Source code in src/depictr/multivariate.py
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | |
dendrogram_plot ¶
dendrogram_plot(data, cols=None, method='ward', title=None)
Hierarchical-clustering dendrogram drawn from a scipy linkage.
The linkage is computed by scipy and the dendrogram coordinates it returns are redrawn as line segments under the depictr theme, so the tree matches the rest of the figure set. Leaves are not labelled, since the intent is to read the overall merge structure rather than individual observations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
cols
|
list of str
|
Numeric columns to cluster on; defaults to all numeric columns. |
None
|
method
|
str
|
Linkage method passed to :func: |
'ward'
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.dendrogram_plot(wb.groupby("region", observed=True).mean(numeric_only=True))
Source code in src/depictr/multivariate.py
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | |
silhouette_plot ¶
silhouette_plot(data, cols=None, k=3, title=None)
Silhouette widths per observation, grouped and ordered by cluster.
k-means is run on the standardised data and the silhouette width of each observation is drawn as a horizontal bar, sorted within its cluster and coloured by cluster. A dashed reference line marks the mean silhouette width, a quick read on how well-separated the clustering is overall.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
cols
|
list of str
|
Numeric columns to use; defaults to all numeric columns. |
None
|
k
|
int
|
Number of clusters. |
3
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> p = dp.silhouette_plot(wb, k=3)
Source code in src/depictr/multivariate.py
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | |
survival_plot ¶
survival_plot(time, event, group=None, conf_level=0.95, risk_table=False, legend_inside=False, title=None, x_lab='Time', y_lab='Survival probability', censor_marks=True, logrank=None, conf_type='log-log')
Kaplan-Meier survival curves, optionally by group, with confidence limits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time
|
array - like
|
Follow-up times. Must be finite: a missing or infinite time raises, since dropping or imputing it is the analyst's decision. |
required |
event
|
array - like
|
Event indicator, coded 0/1 (0 = censored, 1 = event) or with the
|
required |
group
|
array - like
|
Group label per observation; one curve per group, plus a log-rank test of the difference. Observations with a missing group are dropped with a warning, since they belong to no arm. |
None
|
conf_level
|
float or None
|
Confidence level of the pointwise limits drawn as dotted steps around
each curve, as the R twin draws them, strictly between 0 and 1.
|
0.95
|
risk_table
|
bool
|
Add a number-at-risk table as a thin strip beneath the curves. |
False
|
legend_inside
|
bool
|
When |
False
|
title
|
str
|
|
None
|
x_lab
|
str
|
Axis labels. |
'Time'
|
y_lab
|
str
|
Axis labels. |
'Time'
|
censor_marks
|
bool
|
Mark each censored observation with a "+" at the survival level current at its time. |
True
|
logrank
|
bool or None
|
Whether to print the log-rank test of the group difference as a
subtitle when there are groups. |
None
|
conf_type
|
('log-log', 'log', 'plain')
|
The scale on which the limits are built from se, the Greenwood
standard error of log S. |
"log-log"
|
Returns:
| Type | Description |
|---|---|
ggplot
|
The plot carries |
References
Borgan, ร., & Liestรธl, K. (1990). A note on confidence intervals and bands for the survival function based on transformations. Scandinavian Journal of Statistics, 17(1), 35-41.
Examples:
>>> import depictr as dp
>>> ct = dp.clinical_trial()
>>> p = dp.survival_plot(ct["time"], ct["event"], group=ct["arm"], logrank=True)
Source code in src/depictr/survival.py
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | |
acf_plot ¶
acf_plot(x, kind='acf', lags=None, title=None)
Autocorrelation or partial autocorrelation as a stem plot.
The correlations come from statsmodels (acf/pacf). The approximate
95% significance band, plus or minus 1.96 / sqrt(n), is drawn as a shaded
ribbon: stems reaching past it are the candidate lags.
The partial autocorrelations are the Yule-Walker estimates from the sample
autocorrelations, without the small-sample adjustment
(pacf(method="ywm")). They are the values R's pacf() gives, so the
twins draw the same stems. statsmodels' default, the adjusted estimator,
differs from R by up to 0.18 on R's AirPassengers, and statsmodels'
documentation reports that it performed consistently worse than the other
estimators in simulations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Series or array - like
|
The series. Any index is ignored here; only the values are used. Missing values at either end are trimmed; an internal missing value is an error, since dropping it would correlate values across the closed-up gap. The R twin keeps such a value in place and computes the correlations from the pairs of observations both present, with a warning. A frame or 2-D array with one column is read as that column, and one with several columns is refused. |
required |
kind
|
('acf', 'pacf')
|
Autocorrelation or partial autocorrelation. |
"acf"
|
lags
|
int
|
Number of lags to show. Defaults to |
None
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> t = np.arange(120)
>>> series = pd.Series(50 + 0.3 * t + rng.normal(0, 3, 120))
>>> p = dp.acf_plot(series)
Source code in src/depictr/timeseries.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
decompose_plot ¶
decompose_plot(x, period=None, model='additive', title=None)
Seasonal decomposition as a stacked, facetted figure.
statsmodels' seasonal_decompose splits the series into observed, trend,
seasonal and residual components. The four are drawn in one figure, stacked
in a single column with a free y-scale per component (facet_wrap on the
component), so each is legible at its own magnitude.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Series or array - like
|
The series. A datetime or period index sets the x-axis; otherwise the observation number is used. Missing values at either end are trimmed; an internal missing value is an error, since dropping it would shift every later observation and misalign the components. A frame or 2-D array with one column is read as that column, and one with several columns is refused. |
required |
period
|
int
|
Seasonal period. Inferred from a monthly, quarterly or daily index when omitted (12, 4 and 7 respectively; a daily index is assumed weekly). |
None
|
model
|
('additive', 'multiplicative')
|
The decomposition model. |
"additive"
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> t = np.arange(120)
>>> series = pd.Series(
... 50 + 0.3 * t + 10 * np.sin(2 * np.pi * t / 12) + rng.normal(0, 3, 120),
... index=pd.period_range("2016-01", periods=120, freq="M"),
... )
>>> p = dp.decompose_plot(series, period=12)
Source code in src/depictr/timeseries.py
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
seasonal_plot ¶
seasonal_plot(x, period=None, title=None)
Seasonal plot (one line per cycle): value by position in the period.
Each cycle (for example each year of monthly data) is one line across the positions in the period. A cycle that the series starts or ends partway through is drawn as the short line it is. Overlaying the cycles makes the repeating shape and any drift between cycles easy to read.
A monthly, quarterly or daily index carries the calendar. When its
frequency implies the period, the position is read from it: the month for
12, labelled Jan to Dec, the quarter for 4, labelled Q1 to Q4, and the
weekday for 7, labelled Mon to Sun. A series that starts in April then
draws April at position 4, and each cycle runs from January to December,
as in the R twin. A quarterly PeriodIndex with a fiscal year numbers
its quarters from that year's start, as pandas does. Without such an
index, as for a bare array, the positions are counted from the first
observation and labelled by number.
The lines run from a mid blue for the first cycle to a dark blue for the
last, so they read in time order. Even the palest has a contrast of 3.51:1
against white. Past about six cycles, neighbouring cycles take colours too
close to tell apart, and the colours then show the order of the cycles more
than which is which. When individual cycles must be told apart, plot fewer
cycles, or use the R twin's style = "subseries". It gives each season a
panel, in which every cycle keeps its own place on the axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Series or array - like
|
The series. Missing values at either end are trimmed; an internal missing value is an error, since dropping it would assign every later observation to the wrong position in the period. A frame or 2-D array with one column is read as that column, and one with several columns is refused. |
required |
period
|
int
|
Seasonal period. Inferred from a monthly, quarterly or daily index when
omitted (12, 4 and 7 respectively; a daily index is assumed weekly). A
period that is not a whole number, such as 365.25 / 7 for weekly data,
is rounded down to whole seasons with a |
None
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> t = np.arange(120)
>>> series = pd.Series(
... 50 + 0.3 * t + 10 * np.sin(2 * np.pi * t / 12) + rng.normal(0, 3, 120),
... index=pd.period_range("2016-01", periods=120, freq="M"),
... )
>>> p = dp.seasonal_plot(series, period=12)
Source code in src/depictr/timeseries.py
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | |
timeseries_plot ¶
timeseries_plot(x, rolling=None, title=None)
The series as a line, optionally with a rolling-mean overlay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Series or array - like
|
The series. A datetime or period index sets the x-axis; otherwise the
observation number is used. A missing value keeps its place on the axis
and leaves a break in the line. A frame or 2-D array with one column is
read as that column, and one with several columns is refused. The R
twin draws one line per column of a multivariate |
required |
rolling
|
int
|
Window length for a centred moving average, drawn over the series in the accent colour. An odd window averages each observation with the same number of neighbours on either side. An even window, such as 12 for monthly data, gives the 2ร12 moving average, a 12-point average followed by a 2-point one (Hyndman & Athanasopoulos, 2021, Section 3.3), as in the R twin. The two observations at the ends of the 13 it spans count half, which centres the average on its observation, as equal weights over an even window cannot. A window that runs off either end of the series, or reaches a missing value, is missing too, so the overlay breaks where the line does. Omit for the raw line only. |
None
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Hyndman, R. J., & Athanasopoulos, G. (2021). Forecasting: Principles and practice (3rd ed.). OTexts. https://otexts.com/fpp3/
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> t = np.arange(120)
>>> series = pd.Series(50 + 0.3 * t + rng.normal(0, 3, 120))
>>> p = dp.timeseries_plot(series, rolling=12)
Source code in src/depictr/timeseries.py
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 | |
Estimation and model estimates¶
Estimation plots and summary tables, and the model-estimate family: forests, predicted effects, interactions, random effects, posteriors and power.
These plots are shown, with the code that produced them, on the model estimates and diagnostics page of the gallery.
estimation_plot ¶
estimation_plot(data, y, group, reference=None, conf_level=0.95, n_boot=5000, effsize='hedges_g', two_panel=False, title=None, seed=None)
Cumming estimation plot of mean differences.
For each non-reference group, the mean difference from the reference group is
drawn as a point with a bootstrap confidence interval, in that group's
column. The reference's column is left empty, a dashed line marks a
difference of zero, and the standardised effect size is annotated beside
each point. A caption names the interval and the number of resamples. With
two_panel=True, the differences sit beneath a panel of the raw data and
group means, each in its group's column. This is the Cumming estimation plot
(Cumming, 2012) that the R twin draws, and its two panels have independent
outcome axes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
y
|
str
|
Name of the numeric outcome column. |
required |
group
|
str
|
Name of the grouping column. |
required |
reference
|
str
|
The reference (control) group the others are compared with. Defaults to
the first level of |
None
|
conf_level
|
float
|
Confidence level for the bootstrap difference intervals, a single number strictly between 0 and 1. |
0.95
|
n_boot
|
int
|
Number of bootstrap resamples for each difference interval, a positive whole number. The default of 5,000 is the R twin's. |
5000
|
effsize
|
('hedges_g', 'cohens_d', 'none')
|
Standardised effect size annotated beside each difference. Hedges' g is
the small-sample corrected default; pass |
"hedges_g"
|
two_panel
|
bool
|
When |
False
|
title
|
str
|
Plot title. |
None
|
seed
|
int
|
Seed for the bootstrap and, in the two-panel layout, for the jitter of the raw data, so that a seeded figure comes out the same every time. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot or Compose
|
A single panel by default, or a two-panel composition when
|
Examples:
>>> import depictr as dp
>>> cy = dp.crop_yield()
>>> p = dp.estimation_plot(cy, "yield", "treatment", n_boot=200, seed=1)
Source code in src/depictr/estimation.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
summary_table ¶
summary_table(data, vars=None, group=None, digits=1, missing=True, max_levels=20)
Build a "Table 1" descriptive summary.
The first row always reports the sample size (N) overall and per group.
Numeric variables are summarised as mean (SD); categorical variables get one
row per level with count (percent). A Missing, n (%) row follows any
variable that has missing values, unless missing is False.
A datetime column gets a single Range row, written as <earliest> to
<latest> in each column. The cells give the date alone when every time in
the column falls at midnight, and the time to the minute, or to the second
when any time has seconds. A timedelta column is summarised as mean (SD) in
the largest of days, hours, minutes and seconds that its median duration
reaches, and the statistic names the unit, as in Mean (SD), days. The R
twin reports a difftime in the units it carries, which a timedelta
lacks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data. |
required |
vars
|
list of str
|
Columns to summarise. If |
None
|
group
|
str
|
A grouping column; one summary column is produced per level, alongside an
overall column. Categorical levels keep their declared category order and
everything else is sorted, so the table does not depend on the row order.
Records whose group value is missing get a |
None
|
digits
|
int
|
Decimal places for the numeric summaries. |
1
|
missing
|
bool
|
Add a |
True
|
max_levels
|
int
|
When |
20
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Examples:
>>> import depictr as dp
>>> wb = dp.wellbeing_survey()
>>> tab = dp.summary_table(wb, vars=["life_satisfaction", "stress"], group="region")
>>> tab.columns.tolist()[0]
'variable'
Source code in src/depictr/tables.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | |
coefficient_plot ¶
coefficient_plot(model, intercept=False, order='model', conf_level=0.95, title=None, reference_line=None, exponentiate=False, labels=None, interaction='times')
Forest (dot-and-whisker) plot of model coefficients.
By default each term is labelled for a reader. The coefficient of a factor
level names its variable and level, so patsy's treatment[T.enhanced]
is drawn as treatment: enhanced, the contrast of enhanced with the
reference level, and rainfall:treatment[T.enhanced] as
rainfall ร treatment: enhanced. For a formula fit, the levels are read
from the model's design, so a level holding a colon or a bracket is kept
whole. Other names, and the terms of a data frame, are tidied with
:func:format_terms, which also drops the Q() and C() wrappers of
a formula. The R twin labels its terms by the same rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object or pandas.DataFrame
|
See :func: |
required |
intercept
|
bool
|
Whether to include the intercept term. |
False
|
order
|
('model', 'none', 'ascending', 'descending')
|
Order of the terms. |
"model"
|
conf_level
|
float
|
Confidence level passed to :func: |
0.95
|
title
|
str
|
|
None
|
reference_line
|
float
|
Position of the dashed vertical reference line. |
None
|
exponentiate
|
bool
|
Whether to draw the exponentiated estimates and intervals on a log10
axis, which shows the coefficients of a logistic regression as odds
ratios and those of a Poisson or negative binomial model as rate
ratios. The axis is labelled "Odds ratio" for a logit link, "Rate
ratio" for a log link in a count model, "Risk ratio" for a binomial
model with a log link and "exp(Estimate)" otherwise. A data frame
|
False
|
labels
|
(dict, list or 'raw')
|
Display labels for the terms. A dict maps raw term names to labels,
such as |
None
|
interaction
|
('times', 'asterisk', 'colon', 'space')
|
How an interaction is written in the default labels, passed to
:func: |
"times"
|
Returns:
| Type | Description |
|---|---|
ggplot
|
The plot's data keeps the raw names in |
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + treatment', cy).fit()
>>> p = dp.coefficient_plot(model, title="Drivers of crop yield")
>>> list(p.data["label"].cat.categories)[::-1]
['treatment: enhanced', 'fertiliser', 'rainfall']
>>> p = dp.coefficient_plot(model, labels={"treatment[T.enhanced]": "Enhanced"})
Odds ratios from a logistic regression, on a log axis with the line at 1:
>>> ld = dp.lexical_decision()
>>> logit = smf.logit("accuracy ~ word_frequency + condition", ld).fit(disp=0)
>>> p = dp.coefficient_plot(logit, exponentiate=True)
Source code in src/depictr/models.py
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | |
tidy_estimates ¶
tidy_estimates(model, conf_level=0.95, exponentiate=False)
Coerce a fitted model or an estimate table into one tidy frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object or pandas.DataFrame
|
A fitted model exposing |
required |
conf_level
|
float
|
Confidence level for the interval when reading it from a model, a single number strictly between 0 and 1. |
0.95
|
exponentiate
|
bool
|
Whether to exponentiate the estimates and interval limits, which turns
log-odds into odds ratios and log rates into rate ratios. A data frame
is read as estimates on the link scale. Defaults to |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Notes
The intervals are those of the result's conf_int(), so they follow its
use_t. An OLS result uses t. A GLM uses z by default, as statsmodels'
own GLM summary does, and t on the residual degrees of freedom when fitted
with use_t=True. The R twin gives a family whose dispersion is
estimated (Gaussian, Gamma, inverse Gaussian) the t interval that R's
summary.glm() tests with, so fit such a GLM with use_t=True to get
the R twin's intervals. At 12 residual degrees of freedom, the t interval
is about 11% wider than the z interval, and at 100 about 1% wider.
For a mixed model, only the fixed effects are kept, as the R twin keeps
lme4::fixef(). The variance and covariance entries that follow them in
a MixedLM result's params are not coefficients, and statsmodels
reports them divided by the residual scale. The intervals kept are Wald
intervals on the normal distribution, as the R twin computes them for an
lme4 fit.
A model fitted from arrays has unnamed estimates, so its terms are named
from the model's exog_names (const, x1, ...), as statsmodels'
own summary names them. Terms read from a model are always strings, as in
R, so the numbered columns of a data frame made from an array give the
terms "0", "1" and so on.
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> dp.tidy_estimates(model).columns.tolist()
['term', 'estimate', 'conf_low', 'conf_high']
Source code in src/depictr/models.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
effects_plot ¶
effects_plot(model, var, conf_level=0.95, n=100, title=None)
Predicted response as one predictor varies, with a confidence band.
The predictor var is swept across its observed range while every other
numeric predictor is held at its mean and every categorical at its most
common level (the mode). The line is the predicted response and the ribbon its
confidence band, both read from statsmodels' get_prediction. The band
takes t or z as the result's use_t says, so a GLM whose dispersion is
estimated, fitted with use_t=True, gets the t band of the R twin.
The predictors are the columns the formula names, so a column of the source
frame outside the model plays no part. The range, means and levels come from
the rows the model was fitted to, without the rows its missing-value
handling dropped. A formula term computed from statistics of its rows, such
as I(x - x.mean()), is refused with ValueError, since
get_prediction() would compute it afresh over the grid. patsy's
center() and standardize() keep the values of the fit and are safe.
A model fitted with an offset is predicted at the mean offset over the
estimation rows, and one fitted with an exposure at the geometric mean
exposure. The y-axis label then ends in "at the mean offset".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS/GLM result from a formula and a DataFrame (so the source data is retained for building the grid). |
required |
var
|
str
|
The predictor to vary along the x-axis: a numeric column that the formula names. |
required |
conf_level
|
float
|
Confidence level for the band, a single number strictly between 0 and 1. |
0.95
|
n
|
int
|
Number of points across the predictor's range. |
100
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.effects_plot(model, "fertiliser")
Source code in src/depictr/predictions.py
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | |
interaction_plot ¶
interaction_plot(model, x, group, conf_level=0.95, n=100, band=True, title=None)
Predicted response across x for each level of a categorical group.
One coloured line per group shows how the predicted response changes with
x within that group, so a fan of non-parallel lines is the visible mark of
an interaction. The remaining predictors are held at their mean if numeric,
and at their most common level (the mode) if categorical. As in
:func:effects_plot, the predictors are the columns the formula names, the
range, means and levels come from the rows the model was fitted to, a term
computed from statistics of its rows is refused, and an offset or exposure
is held at its mean.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS/GLM result from a formula and a DataFrame. |
required |
x
|
str
|
The numeric predictor on the x-axis. |
required |
group
|
str
|
The categorical predictor whose levels are drawn as separate lines. A level that occurs only in rows the fit dropped is not drawn. |
required |
conf_level
|
float
|
Confidence level for the bands, a single number strictly between 0 and 1. |
0.95
|
n
|
int
|
Number of points across the x range, per group. |
100
|
band
|
bool
|
Whether to draw a confidence ribbon behind each line. |
True
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser * treatment', cy).fit()
>>> p = dp.interaction_plot(model, "fertiliser", "treatment")
Source code in src/depictr/predictions.py
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 | |
compare_models ¶
compare_models(models, intercept=False, conf_level=0.95, title=None, reference_line=None, exponentiate=False, labels=None, interaction='times')
Dodged forest comparing each coefficient across several models.
Each model contributes its estimates and confidence intervals (read with
:func:depictr.models.tidy_estimates, so a fitted model or a tidy frame both
work); the terms share a y-axis and the models are dodged apart and coloured,
so a coefficient that shifts from one specification to the next is easy to
spot.
The terms are labelled as in :func:depictr.coefficient_plot: a factor
level names its variable and level (treatment: enhanced), read from
the design of each fitted model, and other names are tidied with
:func:depictr.format_terms. Terms that take the same label share a row,
so stress and a brms-style b_stress are drawn as one term, as in
the R twin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
dict
|
|
required |
intercept
|
bool
|
Whether to keep the intercept term. |
False
|
conf_level
|
float
|
Confidence level passed to :func: |
0.95
|
title
|
str
|
Plot title. |
None
|
reference_line
|
float
|
Position of the dashed vertical reference line. |
None
|
exponentiate
|
bool
|
Whether to draw every model's exponentiated estimates and intervals on
a log10 axis, as odds or rate ratios. A tidy frame among the models
must hold estimates on the link scale, since it is exponentiated too.
The axis is named as in :func: |
False
|
labels
|
(dict, list or 'raw')
|
Display labels for the terms. A dict maps raw term names to labels and
leaves the other terms to the default labels, and a key that matches
no term warns. A list gives one label per row, in the order the terms
first appear across the models, which is the order they are drawn
from the top, after the intercept is dropped unless
|
None
|
interaction
|
('times', 'asterisk', 'colon', 'space')
|
How an interaction is written in the default labels, passed to
:func: |
"times"
|
Returns:
| Type | Description |
|---|---|
ggplot
|
The plot's data keeps the raw names in |
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> reduced = smf.ols('Q("yield") ~ fertiliser', cy).fit()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.compare_models({"Reduced": reduced, "Full": model})
Source code in src/depictr/predictions.py
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 | |
random_effects_plot ¶
random_effects_plot(model, title=None)
Caterpillar plot of the predicted random effects (BLUPs).
Each point is one group level's predicted random effect, the deviation of that group from the population fit. Levels are sorted by their effect, so the plot reads as a tilted ladder from the most negative to the most positive group, against a zero reference line. A group whose interval clears zero stands apart from the average; one straddling zero does not.
The points come straight from model.random_effects. When the fit exposes
the conditional covariance of each group's effects
(model.random_effects_cov), the diagonal gives a conditional standard
error and the plot adds a 95% interval (the point estimate plus or minus
1.96 standard errors); without it the points are drawn on their own. If the
model has more than one random-effect term per group (a random intercept and
one or more random slopes), the terms are shown in separate panels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels MixedLMResults
|
A fitted |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Notes
The interval is the conditional one around each predicted effect, not a confidence interval for a fixed parameter. It reflects how precisely that group's effect is pinned down given the fitted variance components.
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> ld = dp.lexical_decision()
>>> mm = smf.mixedlm("RT ~ condition", ld, groups=ld["participant"]).fit()
>>> p = dp.random_effects_plot(mm, title="Participant intercepts")
Source code in src/depictr/mixed.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
posterior_plot ¶
posterior_plot(draws, labels=None, title=None)
Forest plot of posterior (or bootstrap) draws.
Each parameter is one row: the median as a point, a thick inner band for the central 66% credible interval and a thin outer band for the central 95%. Both are equal-tailed (quantile) intervals, which leave the same share of the draws in either tail. The first parameter reads at the top.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
draws
|
pandas.DataFrame or dict of array-like
|
The draws, one column (or dict entry) per parameter. A dict maps a parameter name to a 1-D array of draws; the arrays may differ in length. |
required |
labels
|
dict
|
Remap raw parameter names to display names, |
None
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> draws = {"stress": rng.normal(-0.4, 0.1, 500),
... "sleep_hours": rng.normal(0.3, 0.1, 500)}
>>> p = dp.posterior_plot(draws)
Source code in src/depictr/posterior.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
frequentist_bayesian_plot ¶
frequentist_bayesian_plot(frequentist, bayesian, title=None)
Overlay a frequentist estimate against a Bayesian posterior per term.
For each term the two sources share a row, offset slightly so they do not overlap. The frequentist side shows the point estimate with its confidence interval; the Bayesian side shows the posterior median with the inner 66% and outer 95% credible intervals. The sources are told apart by colour (brand blue for frequentist, the palette's orange for Bayesian).
Only terms present in both sources are drawn. The frequentist confidence
level is whatever :func:depictr.models.tidy_estimates reads from the model
(95% for a fitted statsmodels result), matching the Bayesian outer band.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequentist
|
statsmodels results object or pandas.DataFrame
|
A fitted model or a tidy estimate frame, as accepted by
:func: |
required |
bayesian
|
pandas.DataFrame or dict of array-like
|
Posterior draws, one column (or dict entry) per term, as accepted by
:func: |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> rng = np.random.default_rng(0)
>>> draws = {t: rng.normal(model.params[t], model.bse[t], 500)
... for t in model.params.index if t != "Intercept"}
>>> p = dp.frequentist_bayesian_plot(model, draws)
Source code in src/depictr/posterior.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
power_curve_plot ¶
power_curve_plot(data, n=None, power=None, group=None, title=None, target=_TARGET_POWER, lower=None, upper=None, ribbon=True, y_lab=None, solved=None)
Line plot of statistical power against sample size.
Reads a precomputed tidy table and draws power as a function of sample size, with a dashed reference line at the target power (0.8 by default). Where the curve crosses the line is the sample size that reaches adequate power.
A table may hold several curves, one per effect or design, stacked by row.
Each is drawn as its own line, told apart by colour and line type. The
tables pilotr returns are read as they are: power_curve_mixed() gives
n_subject, effect, power, power_lo and power_hi, and
precision_curve() gives n_subject, param and the decision
probabilities p_meaningful and p_equivalent with their own _lo
and _hi bounds, which power can name. The sample size that pilotr's
target_n() solves from such a curve, and its interval, can be marked on
the plot with solved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
pandas.DataFrame or list of dict
|
A tidy table with one row per sample size (per curve, if there are
several). A list of dicts, one per row, is read with
:class: |
required |
n
|
str
|
Name of the sample-size column. By default, the first of |
None
|
power
|
str
|
Name of the power column, on the 0-1 scale. By default, the first of
|
None
|
group
|
str
|
A column that separates several curves. Each level is drawn as its own
line, in its own colour and line type. By default, an |
None
|
title
|
str
|
Plot title. |
None
|
target
|
float
|
Target power, drawn as a horizontal reference line. |
_TARGET_POWER
|
lower
|
str
|
Columns holding the lower and upper confidence limits. Name both or
neither. By default, |
None
|
upper
|
str
|
Columns holding the lower and upper confidence limits. Name both or
neither. By default, |
None
|
ribbon
|
bool
|
Whether to draw the confidence band as a shaded ribbon ( |
True
|
y_lab
|
str
|
Y-axis label. By default, "Power" when the power column is one of the default names, and the column's name otherwise. |
None
|
solved
|
dict or DataFrame
|
A sample size solved from the curve, with its confidence interval: a
dict with numeric |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Cohen, J. (1988). Statistical power analysis for the behavioral sciences (2nd ed.). Lawrence Erlbaum Associates.
Examples:
>>> import depictr as dp
>>> import pandas as pd
>>> from statsmodels.stats.power import TTestIndPower
>>> analysis = TTestIndPower()
>>> power = pd.DataFrame({"n": range(25, 425, 25)})
>>> power["power"] = [analysis.power(effect_size=0.5, nobs1=n, alpha=0.05)
... for n in power["n"]]
>>> p = dp.power_curve_plot(power)
The sample size at which the curve reaches 80% power, with its interval,
in the form pilotr's target_n() returns. The interval here is
illustrative:
>>> p = dp.power_curve_plot(power, solved={"value": 64, "lo": 57, "hi": 72})
Two curves in one table, laid out as pilotr's power_curve_mixed()
returns them, one row per sample size and effect:
>>> rows = [
... {"n_subject": n, "effect": e, "power": pw,
... "power_lo": pw - 0.08, "power_hi": pw + 0.08}
... for n, e, pw in [(20, "condition", 0.31), (20, "frequency", 0.12),
... (40, "condition", 0.58), (40, "frequency", 0.21),
... (60, "condition", 0.78), (60, "frequency", 0.33)]
... ]
>>> p = dp.power_curve_plot(rows, group="effect")
Source code in src/depictr/power.py
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | |
Diagnostics¶
Residual and influence diagnostics, and a one-figure model report.
These plots are shown, with the code that produced them, on the model estimates and diagnostics page of the gallery.
qq_plot ¶
qq_plot(model, title=None)
Normal quantile-quantile plot of the residuals, with a reference line.
The sample residual quantiles are plotted against the matching standard-normal
quantiles. Points near the line mean the residuals are close to normal; a
systematic bend away from it flags skew or heavy tails. The line is drawn
through the first and third quartiles, the robust choice R's qqline uses,
so a few outliers do not tilt it.
The residuals of a linear model should follow the line. The studentised
residuals of a non-Gaussian GLM are not expected to, even when the model
is correctly specified: those of a binary model take only two values at
each fitted value. A bend then says little about the fit. For a GLM,
:func:depictr.residual_diagnostics_plot and :func:depictr.model_report
plot randomised quantile residuals (Dunn & Smyth, 1996), which are standard
normal under a correct model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object or array-like
|
A fitted OLS/GLM result, from which the internally studentised residuals are read (the raw residuals if studentised ones are not available), or a 1-D array of residuals to plot directly. |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Dunn, P. K., & Smyth, G. K. (1996). Randomized quantile residuals. Journal of Computational and Graphical Statistics, 5(3), 236-244. https://doi.org/10.1080/10618600.1996.10474708
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.qq_plot(model)
Source code in src/depictr/diagnostics.py
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |
influence_plot ¶
influence_plot(model, title=None)
Bubble plot of studentised residuals against leverage.
Each point is one observation; the bubble area is proportional to its Cook's distance, the standard measure of how much the fit would move if the point were dropped (Cook, 1977). Points to the right have high leverage (an unusual predictor combination), points far above or below zero are poorly fitted, and large bubbles are the ones that combine both into real influence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS/GLM result. |
required |
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Cook, R. D. (1977). Detection of influential observation in linear regression. Technometrics, 19(1), 15-18. https://doi.org/10.1080/00401706.1977.10489493
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.influence_plot(model)
Source code in src/depictr/diagnostics.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | |
vif_plot ¶
vif_plot(model, threshold=5, title=None)
Horizontal bar chart of the variance inflation factor of each model term.
The variance inflation factor (VIF) measures how much collinearity with
the other predictors inflates the variance of a coefficient. A VIF of 1
means no collinearity, and the reference line marks threshold, 5 by
default, a common rule of thumb above which collinearity is treated as a
concern.
A term that spans several design columns, such as a factor with more than
two levels, gets one generalised VIF, the GVIF of Fox and Monette (1992).
Let R be the correlation matrix of the coefficient estimates other than
the intercept, R11 its block for the term and R22 the block for the other
terms. Then GVIF = det(R11) det(R22) / det(R). Unlike the VIFs of a
factor's separate dummy columns, it does not depend on which level is the
reference. For a term of one column, it is the ordinary VIF. When any term
spans several columns, the bars show GVIF^(1/(2 df)) and the line moves to
the square root of threshold. GVIF^(1/(2 df)) puts terms of different
sizes on one scale, and for a single column it is the square root of the
VIF.
The correlations come from the fit's unscaled coefficient covariance,
normalized_cov_params, so a weighted fit or a GLM is measured with the
weights of its fit. For an unweighted linear model with an intercept, they
give the values the formula gives with R the correlation matrix of the
predictor columns, as Fox and Monette state it. The values are those of
the R twin and of R's car::vif(). A Probit result differs slightly,
because statsmodels takes its covariance from the observed information
matrix, where R's glm() uses the expected one. A GLM with a probit
link gives R's values.
The columns are grouped into terms by the model's formula, whether
statsmodels built it with patsy or with formulaic. A model fitted from
arrays has no formula, so each column is a term of its own, and the dummy
columns of a factor are not grouped. A model whose coefficients are
aliased has no VIF, so it raises ValueError naming the terms they
belong to, as does a model with fewer than two predictor columns. A pandas
categorical keeps a category that no row takes, after rows are filtered
out for instance. Such a category fills a column of zeros, which counts as
aliased, so drop it with Series.cat.remove_unused_categories() before
the fit. R's lm() drops such a level itself. A model without an
intercept warns, because its VIFs then come from uncentred columns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS, WLS, GLM, Logit or Probit result with two or more predictor columns. |
required |
threshold
|
float
|
The VIF taken as the rule of thumb, drawn as the reference line. On
the GVIF^(1/(2 df)) scale, it is drawn at |
5
|
title
|
str
|
Plot title. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
Its |
References
Fox, J., & Monette, G. (1992). Generalized collinearity diagnostics. Journal of the American Statistical Association, 87(417), 178-183. https://doi.org/10.1080/01621459.1992.10475190
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.vif_plot(model)
A factor with several levels gets one generalised VIF:
>>> ws = dp.wellbeing_survey()
>>> fit = smf.ols("life_satisfaction ~ stress + region + education", ws).fit()
>>> p = dp.vif_plot(fit)
Source code in src/depictr/diagnostics.py
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | |
binned_residual_plot ¶
binned_residual_plot(model, n_bins=None, title=None, band='model')
Binned residual plot for a binomial model (Gelman & Hill, 2007).
Raw residuals from a logistic model are uninformative point by point, so the fitted probabilities are split into equal-count bins and the mean residual is plotted against the mean fitted value in each bin. The bins are the R twin's. Rows are ranked by their fitted probability with ties in row order, and the ranks are cut at multiples of n / n_bins. A row with zero weight takes no part in the fit and is left out.
The grey band is plus or minus two standard errors of a bin's mean
residual. By default (band="model"), they are the standard errors the
fitted model implies. The model gives a row with fitted probability p_i
over m_i trials, taken as a proportion, the variance
scale * p_i * (1 - p_i) / m_i. Taking this as the variance of the row's
residual, the band gives the mean of the n_b residuals in a bin the
standard error sqrt(sum of their variances) / n_b. The fit absorbs a little
of each row's variance, so a residual varies slightly less than this and
the band errs on the wide side. Under a well-fitting model, about 95% of
the points fall inside the band. A run of points outside it, or a clear
trend in them, points to a missing predictor or the wrong functional form.
The scale is 1 unless the GLM was fitted with an estimated one, the
counterpart of the dispersion of R's quasibinomial family. statsmodels'
Pearson estimate, fit(scale="X2"), leaves out the trials of a
two-column (successes, failures) response, so for such a fit the scale is
recomputed as pearson_chi2 / df_resid, which counts them, as R does.
band="empirical" takes the standard error from the residuals in the bin,
as their standard deviation over sqrt(n_b), the band of R's
arm::binnedplot(). It collapses in a bin whose outcomes are all alike,
such as a bin without events, and so flags such bins even when the model is
right.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted binomial GLM (logistic regression), or a Logit or Probit result, whose fitted probabilities are used. |
required |
n_bins
|
int
|
Number of bins, at least 2. Defaults to the square root of the sample size, rounded down and kept between 2 and half the sample size, the rule of thumb in Gelman & Hill. |
None
|
title
|
str
|
Plot title. |
None
|
band
|
('model', 'empirical')
|
Where the standard errors come from: the variance the fitted model implies for each residual, or the spread of the residuals in each bin. |
"model"
|
Returns:
| Type | Description |
|---|---|
ggplot
|
Its |
References
Gelman, A., & Hill, J. (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press.
Examples:
>>> import depictr as dp
>>> import statsmodels.api as sm
>>> import statsmodels.formula.api as smf
>>> ct = dp.clinical_trial()
>>> glm = smf.glm("adverse_event ~ biomarker + age", ct,
... family=sm.families.Binomial()).fit()
>>> p = dp.binned_residual_plot(glm)
Source code in src/depictr/diagnostics.py
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 | |
residual_diagnostics_plot ¶
residual_diagnostics_plot(model, title=None, glm_panels=True, seed=None)
Four-panel residual report for a fitted OLS, GLM, Logit or Probit model.
Composes the four checks R draws from plot.lm into a 2x2 grid: residuals
against fitted values, the scale-location plot, a normal quantile-quantile
plot of the residuals, and residuals against leverage. Read together they
cover the usual assumptions of a linear fit: the right functional form,
constant variance, approximately normal residuals, and no single point
dominating the fit.
A GLM, Logit or Probit fit gets panels made for it (glm_panels=True), as
in the R twin. The Q-Q panel plots randomised quantile residuals (Dunn &
Smyth, 1996), which are standard normal under a correctly specified model
whatever the family. The studentised residuals of a binary or count model
are not expected to be normal, even when the model is right. The quantile
residuals of a binomial or Poisson fit are randomised, so a departure worth
acting on is one that persists when the panel is drawn again with another
seed. A binomial model's residual panel is
:func:depictr.binned_residual_plot (Gelman & Hill, 2007). Other families
plot their deviance residuals against the fitted values. The R twin bins
them too, but the binned plot here takes binomial models only. A family
other than the binomial, Poisson, Gaussian and Gamma has no quantile
residual here. Its Q-Q panel plots the deviance residuals, and the axis
says so. A Logit or Probit fit is plotted against its fitted probabilities,
as the binomial GLM of the same model is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS, GLM, Logit or Probit result, such as
|
required |
title
|
str
|
Accepted for API symmetry, but dropped with a warning: plotnine compositions cannot carry a figure-level title, so the grid keeps its per-panel titles instead. |
None
|
glm_panels
|
bool
|
For a GLM, Logit or Probit fit, whether to draw the quantile-residual
and binned-residual panels. |
True
|
seed
|
int
|
Seed for the randomisation of the quantile residuals of a binomial or Poisson fit, for a reproducible Q-Q panel. numpy's global random state is left alone. |
None
|
Returns:
| Type | Description |
|---|---|
Compose
|
A 2x2 composition with |
References
Dunn, P. K., & Smyth, G. K. (1996). Randomized quantile residuals. Journal of Computational and Graphical Statistics, 5(3), 236-244. https://doi.org/10.1080/10618600.1996.10474708
Fox, J., & Weisberg, S. (2019). An R companion to applied regression (3rd ed.). Sage.
Gelman, A., & Hill, J. (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press. https://doi.org/10.1017/CBO9780511790942
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.residual_diagnostics_plot(model)
A logistic GLM gets binned residuals and a Q-Q plot of quantile residuals, whose randomisation the seed fixes:
>>> import statsmodels.api as sm
>>> ct = dp.clinical_trial()
>>> glm = smf.glm("adverse_event ~ biomarker + age + arm", ct,
... family=sm.families.Binomial()).fit()
>>> p = dp.residual_diagnostics_plot(glm, seed=1)
Source code in src/depictr/diagnostics_panels.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
model_report ¶
model_report(model, title=None, glm_panels=True, seed=None)
One-figure dashboard pairing the coefficient plot with key diagnostics.
Puts the three plots a reader usually wants first side by side: the
coefficient (forest) plot of the estimates and their confidence intervals,
a residual panel, and the normal quantile-quantile plot. It is a quick "is
the model worth trusting, and what does it say" view, with the full set of
checks left to :func:residual_diagnostics_plot. A GLM, Logit or Probit
fit gets the panels that function draws for it: a Q-Q plot of randomised
quantile residuals and, for a binomial model, binned residuals in place of
the scatter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
statsmodels results object
|
A fitted OLS, GLM, Logit or Probit result exposing |
required |
title
|
str
|
Accepted for API symmetry, but dropped with a warning: plotnine compositions cannot carry a figure-level title, so the dashboard keeps its per-panel titles instead. |
None
|
glm_panels
|
bool
|
For a GLM, Logit or Probit fit, whether to draw the quantile-residual
and binned-residual panels. |
True
|
seed
|
int
|
Seed for the randomisation of the quantile residuals of a binomial or Poisson fit, for a reproducible Q-Q panel. numpy's global random state is left alone. |
None
|
Returns:
| Type | Description |
|---|---|
Compose
|
A composition with |
Examples:
>>> import depictr as dp
>>> import statsmodels.formula.api as smf
>>> cy = dp.crop_yield()
>>> model = smf.ols('Q("yield") ~ fertiliser + rainfall + soil_ph', cy).fit()
>>> p = dp.model_report(model)
>>> ct = dp.clinical_trial()
>>> logit = smf.logit("adverse_event ~ biomarker + age + arm", ct).fit(disp=0)
>>> p = dp.model_report(logit, seed=1)
Source code in src/depictr/diagnostics_panels.py
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
Classification¶
The standard classification curves and tables, most of them computed by scikit-learn and all redrawn under the shared theme. The gains, lift and calibration curves are computed as the R twin computes them.
These plots are shown, with the code that produced them, on the classification and survival page of the gallery.
roc_curve_plot ¶
roc_curve_plot(y_true, y_score, title=None)
ROC curve with the area under the curve (AUC) annotated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two categories, whose last category is the positive class, as in the R twin. Both classes must be present: with only one, the true or false positive rate has no denominator and the AUC is undefined. |
required |
y_score
|
array - like
|
Predicted scores or probabilities for the positive class. |
required |
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.roc_curve_plot(ct["adverse_event"], score)
Source code in src/depictr/classification.py
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | |
pr_curve_plot ¶
pr_curve_plot(y_true, y_score, title=None)
Precision-recall curve with the average precision (AP) annotated.
The dashed horizontal line is the positive rate, the precision a random classifier achieves, which is the baseline a precision-recall curve is read against (unlike a ROC curve, whose baseline is fixed at the diagonal).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two categories, whose last category is the positive class, as in the R twin. Both classes must be present: with only one, precision or recall has no denominator and the average precision is undefined. |
required |
y_score
|
array - like
|
Predicted scores or probabilities for the positive class. |
required |
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.pr_curve_plot(ct["adverse_event"], score)
Source code in src/depictr/classification.py
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
confusion_matrix_plot ¶
confusion_matrix_plot(y_true, y_pred, normalise=None, title=None)
Confusion-matrix heatmap.
Each count is drawn in black or white, whichever has the higher contrast against its cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
True and predicted labels. |
required |
y_pred
|
array - like
|
True and predicted labels. |
required |
normalise
|
(None, 'true', 'pred', 'all')
|
Passed to scikit-learn's |
None
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.confusion_matrix_plot(ct["adverse_event"], (score > 0.6).astype(int))
Source code in src/depictr/classification.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | |
calibration_plot ¶
calibration_plot(y_true, y_score, n_bins=10, title=None, conf_level=0.95, strategy='quantile', bins=None)
Reliability (calibration) curve of predicted against observed frequencies.
The cases are grouped into bins by their predicted probability, and each bin is drawn at its mean predicted probability and its observed event rate, with the dashed diagonal marking perfect calibration. By default the bins hold equal numbers of cases, as in the R twin, so each point rests on about the same evidence. Each point is sized by its number of cases and carries a Wilson interval for its observed rate (Wilson, 1927), so a bin backed by few cases is not over-read. Brown et al. (2001) recommend the Wilson interval for small samples. Its coverage stays close to the nominal level where the Wald interval's is erratic, though it dips for rates very close to 0 or 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two
categories, whose last category is the positive class, as in the R
twin. Any other value, a missing one included, raises |
required |
y_score
|
array - like
|
Predicted probabilities from a fitted model, on the probability scale.
Unlike the ROC and gains charts, which only rank cases, a reliability
curve compares the predicted probability with the observed frequency,
so an arbitrary monotone score would misstate the calibration. A value
outside [0, 1], or a missing one, raises |
required |
n_bins
|
int
|
Number of bins requested. Fewer points are drawn when bins merge or
come out empty (see |
10
|
title
|
str
|
|
None
|
conf_level
|
float or None
|
Confidence level of the Wilson intervals, strictly between 0 and 1.
|
0.95
|
strategy
|
('quantile', 'uniform')
|
How the bins are cut. |
"quantile"
|
bins
|
int
|
The R twin's name for |
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
Its data hold one row per bin, with the columns |
References
Brown, L. D., Cai, T. T., & DasGupta, A. (2001). Interval estimation for a binomial proportion. Statistical Science, 16(2), 101-133. https://doi.org/10.1214/ss/1009213286
Wilson, E. B. (1927). Probable inference, the law of succession, and statistical inference. Journal of the American Statistical Association, 22(158), 209-212. https://doi.org/10.1080/01621459.1927.10502953
Examples:
>>> import depictr as dp
>>> from sklearn.linear_model import LogisticRegression
>>> ct = dp.clinical_trial()
>>> X = ct[["biomarker", "age"]]
>>> fit = LogisticRegression().fit(X, ct["adverse_event"])
>>> p = dp.calibration_plot(ct["adverse_event"],
... fit.predict_proba(X)[:, 1], n_bins=5)
Source code in src/depictr/classification.py
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 | |
gain_plot ¶
gain_plot(y_true, y_score, title=None)
Cumulative gains chart: positives captured as more of the ranked population is targeted.
The population is sorted by y_score, best first, and the curve traces
the share of all positive cases captured by each depth. The dashed diagonal
is random targeting.
Cases with tied scores are targeted together, as in the R twin. The curve takes a point only once every case of a run of equal scores is in, and it runs straight to that point, which is the share expected when tied cases are reached in random order (Fawcett, 2006). The curve therefore does not depend on the order of the rows. With every score distinct, each case is a point of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two
categories, whose last category is the positive class, as in the R
twin. Both classes must be present: with no positives the share
captured has no denominator, and with no negatives every depth captures
everything. A missing outcome raises |
required |
y_score
|
array - like
|
Predicted scores or probabilities for the positive class, one per
outcome. Only the ranking matters, so any monotone score works. A
missing score, which has no rank, raises |
required |
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
References
Fawcett, T. (2006). An introduction to ROC analysis. Pattern Recognition Letters, 27(8), 861-874. https://doi.org/10.1016/j.patrec.2005.10.010
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.gain_plot(ct["adverse_event"], score)
Source code in src/depictr/classification.py
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | |
lift_plot ¶
lift_plot(y_true, y_score, title=None)
Cumulative lift chart.
Lift is the share of positives captured at a given depth of the score-ordered population, divided by that depth. A lift of 3 in the top 10% means that decile holds three times the baseline positive rate. The dashed line at 1 is random targeting.
Cases with tied scores are targeted together, as in
:func:depictr.gain_plot and the R twin, so the curve has a point at the
end of each run of equal scores and does not depend on the order of the
rows. It starts at the first such point, since lift has no value at a depth
of zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two
categories, whose last category is the positive class, as in the R
twin. Both classes must be present: lift is a ratio to the baseline
positive rate, which is zero with no positives and one at every depth
with no negatives. A missing outcome raises |
required |
y_score
|
array - like
|
Predicted scores or probabilities for the positive class, one per
outcome. Only the ranking matters, so any monotone score works. A
missing score, which has no rank, raises |
required |
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.lift_plot(ct["adverse_event"], score)
Source code in src/depictr/classification.py
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 | |
threshold_plot ¶
threshold_plot(y_true, y_score, title=None)
Sensitivity, specificity, precision and F1 across the decision threshold.
Sweeps the probability cut-off and plots each metric, so the trade-off when choosing an operating point can be read straight off the curves.
A case is predicted positive when its score is at or above the threshold. The metrics are evaluated at every distinct score, the points at which the confusion matrix can change, so the curves are exact step functions. Between two adjacent scores, each metric keeps its value at the upper one, so each step changes at a score and runs flat below it. Where no positive case is predicted positive, precision and recall are 0 and F1 is taken as 0. The R twin computes and draws the same values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y_true
|
array - like
|
Binary outcomes, coded 0/1, as booleans or as a categorical with two
categories, whose last category is the positive class, as in the R
twin. Both classes must be present: with only one, sensitivity or
specificity has no denominator at any threshold. A missing outcome
raises |
required |
y_score
|
array - like
|
Predicted scores or probabilities for the positive class, one per
outcome. A missing score raises |
required |
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
ggplot
|
|
Examples:
>>> import depictr as dp
>>> import numpy as np
>>> ct = dp.clinical_trial()
>>> score = 1 / (1 + np.exp(-ct["biomarker"]))
>>> p = dp.threshold_plot(ct["adverse_event"], score)
Source code in src/depictr/classification.py
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 | |
Theme, palette and accessibility¶
The shared theme and the colourblind-safe palette, the tools that verify the palette stays distinguishable under colour-vision deficiency, the audit that checks a finished figure rather than the palette it was built from, and the helper that tidies model term names into labels.
The palette, the colour-vision checks and the figure audit are shown in use on the accessibility page of the gallery.
theme_depictr ¶
theme_depictr(base_size=11, grid='xy', base_family=None)
Return the depictr plotnine theme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_size
|
float
|
Base font size in points. |
11
|
grid
|
('xy', 'x', 'y', 'none')
|
Which major gridlines to keep. |
"xy"
|
base_family
|
str
|
Base font family, such as |
None
|
Returns:
| Type | Description |
|---|---|
theme
|
A theme object to add to any plot with |
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.ecdf_plot(ld, "RT") + dp.theme_depictr(base_size=13, grid="y")
>>> p = dp.ecdf_plot(ld, "RT") + dp.theme_depictr(base_family="serif")
Source code in src/depictr/theme.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
scale_colour_depictr ¶
scale_colour_depictr(n=None, order='default', **kwargs)
A discrete colour scale drawn from :func:depictr.palette.depictr_palette.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of colours to draw. By default, the scale takes as many colours
as the plot has groups: the Okabe-Ito colours in order up to eight,
and past eight a palette interpolated through them. Interpolation loses
the colourblind-safety guarantee, which covers the eight base colours
only, so :func: |
None
|
order
|
('default', 'contrast')
|
|
"default"
|
**kwargs
|
Passed to :func: |
{}
|
Examples:
>>> import depictr as dp
>>> from plotnine import aes, geom_point, ggplot
>>> cy = dp.crop_yield()
>>> p = (ggplot(cy, aes("rainfall", "yield", colour="treatment"))
... + geom_point() + dp.scale_colour_depictr())
The vermillion in the orange's place, at 3.87:1 against white:
>>> p = (ggplot(cy, aes("rainfall", "yield", colour="treatment",
... shape="treatment"))
... + geom_point() + dp.scale_colour_depictr(order="contrast"))
Source code in src/depictr/theme.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
scale_fill_depictr ¶
scale_fill_depictr(n=None, order='default', **kwargs)
A discrete fill scale drawn from :func:depictr.palette.depictr_palette.
See :func:scale_colour_depictr for the parameters.
Examples:
>>> import depictr as dp
>>> from plotnine import aes, geom_bar, ggplot
>>> wb = dp.wellbeing_survey()
>>> p = (ggplot(wb, aes("education", fill="region"))
... + geom_bar() + dp.scale_fill_depictr())
Source code in src/depictr/theme.py
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
legend_inside ¶
legend_inside(corner='top right')
Theme fragment that moves the legend into the plotting area.
Places the legend in a corner the plot's geometry usually leaves empty, on
a solid white panel with a light border, so the figure needs no separate
legend column.
The functions that expose a legend_inside argument add this for you.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corner
|
('top right', 'top left', 'bottom right', 'bottom left')
|
Which corner to use, or an explicit |
"top right"
|
Returns:
| Type | Description |
|---|---|
theme
|
|
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.ecdf_plot(ld, "RT", group="condition") + dp.legend_inside("bottom right")
Source code in src/depictr/theme.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
depictr_palette ¶
depictr_palette(n=None, kind='qualitative', order='default')
Return a depictr palette.
In the default order the second colour is the orange (#e69f00), at
2.25:1 against white. Any figure on white whose colour tells two or more
groups apart therefore fails the geometry_contrast row of
:func:depictr.check_figure. No more than three of the eight colours pass
every colour row of that audit together.
order="contrast" puts those three first: the brand blue, the vermillion
(#d55e00) and the reddish purple (#cc79a7), then the bluish green
(#009e73) and the other four in their default order. Up to three groups
then pass every colour row, contrast against white included. They stay at
least 14.01 apart in CIEDE2000 under every vision type, 6.86 apart in
lightness and 3.06:1 against white. Four groups keep 3:1 against white,
but the bluish green and the vermillion are 3.31 apart in lightness, which
fails greyscale_separability. Under this order, the second group is
drawn in the vermillion, which is also :func:depictr.depictr_accent.
The default order is unchanged, and the R twin gives the same order.
The R twin can make the order the default for every plot through its
options. Python has none, so add
dp.scale_colour_depictr(order="contrast"), or the fill scale, to each
plot that should use it. plotnine then says that it replaces the plot's
colour scale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of colours to return, a single number of at least 1. A
fraction is truncated, as in the R twin, so 2.7 gives two colours. For
the qualitative palette |
None
|
kind
|
('qualitative', 'sequential', 'diverging')
|
The palette family. |
"qualitative"
|
order
|
('default', 'contrast')
|
The order of the qualitative palette. |
"default"
|
Returns:
| Type | Description |
|---|---|
list of str
|
Hex colour codes. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an |
Warns:
| Type | Description |
|---|---|
UserWarning
|
When |
Examples:
>>> import depictr as dp
>>> dp.depictr_palette(3)
['#005b96', '#e69f00', '#009e73']
>>> dp.depictr_palette(3, order="contrast")
['#005b96', '#d55e00', '#cc79a7']
Source code in src/depictr/palette.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
depictr_brand ¶
depictr_brand()
Return the depictr brand colour as a hex string.
Examples:
>>> import depictr as dp
>>> dp.depictr_brand()
'#005b96'
Source code in src/depictr/palette.py
76 77 78 79 80 81 82 83 84 85 | |
depictr_accent ¶
depictr_accent()
Return the depictr accent colour as a hex string.
The accent marks a single element against the brand blue, such as the
flagged points of :func:depictr.outlier_plot and the missing cells of
:func:depictr.missingness_map. It is the Okabe-Ito vermillion, as in the
R twin, which stays distinguishable from the brand blue under the common
colour-vision deficiencies and reaches 3.87:1 against white.
Examples:
>>> import depictr as dp
>>> dp.depictr_accent()
'#d55e00'
Source code in src/depictr/palette.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
check_figure ¶
check_figure(plot, width_cm=17.78, render_width_cm=17.78, min_delta_e=5, min_text_pt=6, metric='ciede2000', max_text_pt=None)
Audit a finished figure for accessibility and honesty.
Checks a figure as it will be submitted, rather than the palette it was
built from. Every row carries the value it measured next to the threshold it
was measured against, so a verdict can be argued with rather than merely
accepted. A check passes when the measured value is at least the threshold.
The one exception is text_size_max, a ceiling added when
max_text_pt is given, which passes when the measured value is at most
its threshold. A check with nothing to measure, such as colour separability
on a figure that encodes nothing by colour, reports NaN and a verdict
of "not applicable" instead of a free pass.
Colours and text are measured as drawn. A translucent colour is composited over the background it is drawn on with the alpha it is drawn with, so it is measured as the paler colour the reader sees. As plotnine draws them, bars, tiles, areas, densities, ribbons, polygons, violins, boxplots, crossbars and smooths apply a layer's alpha to their fill and leave the outline opaque. Lines, paths, segments, error bars and reference lines apply it to their colour, and points apply it to both. Any other geom is taken to apply it to both, the cautious assumption. A colour that carries its own alpha keeps it, as plotnine draws it. A colour drawn at several alphas within one layer is measured at the smallest. Where marks overlap one another, the colour that shows depends on what lies beneath it, and that is not measured.
colour_separability is the smallest colour difference between two of
the figure's encoding colours, by the CIEDE2000 formula (Sharma, Wu &
Dalal, 2005) unless metric asks for the older CIE76. The three
colour_separability_* rows repeat that measurement after simulating each
dichromacy at full severity with :func:depictr.simulate_cvd. Encoding
colours are the distinct colour and fill values that tell groups apart. A
layer contributes its colours when it maps colour or fill to a discrete
scale, even if it draws a single colour, or when it draws two or more.
Groups drawn one per layer are therefore audited as they would be in one
layer. A colour set as a constant, as in geom_point(colour="red"), is
left out, even when another layer sets a different one, and so is a lone
colour passed through scale_colour_identity(). The audit cannot tell
such a colour from decoration, so a colour meant to tell groups apart
should be mapped, as aes(colour="'treated'") maps a constant, with
scale_colour_manual() to fix its values. The outline colours, across
every layer, are compared among themselves, and so are the fill colours,
and the row reports the closest pair from either set. An outline is
therefore never compared with its own fill, nor a group with itself as
another layer draws it.
A continuous colour or fill scale, such as a heatmap's, is a smooth ramp
rather than a set of codes, and none of the colour rows audits it. When
nothing else in the figure encodes by colour, each of them abstains with a
detail that says so. Whether a ramp reads in order, to a reader with a
colour-vision deficiency and in greyscale as well, depends on how evenly
its lightness changes, which the audit does not measure. Prefer a
perceptually ordered map, such as viridis or depictr's sequential and
diverging ramps, over a rainbow (Crameri et al., 2020).
greyscale_separability is the smallest difference in CIE lightness
between those same colours, which is what survives printing in black and
white. It is the absolute difference in lightness, whichever metric the
colour rows use.
text_size is the smallest point size of the text the theme draws around
the panel: axis labels and titles, legends, facet strips, titles and
captions. It is scaled by width_cm / render_width_cm, so a figure drawn
seven inches wide and printed in an 8.9 cm column has every point size
halved. Text drawn inside the panel is measured by
annotation_text_size.
text_contrast is the WCAG 2.2 contrast ratio of the text that falls
furthest short of its own threshold. Each text is measured against the
background it is drawn on: a facet strip's fill for strip text, the legend
box's fill for legend text, and the plot background for the rest. A
translucent fill is measured as the colour it shows over what lies beneath
it, and so is text drawn with an alpha. Text that is large at its printed
size, 18 pt or 14 pt in bold, is held to 3:1, and other text to 4.5:1, as
success criterion 1.4.3 asks. The row's threshold is the one that
applies to the text it reports, and its detail names that text's
colour, background, printed size and weight.
geometry_contrast is the smallest WCAG 2.2 contrast ratio of any
encoded group's drawn colour against the panel background, or against the
plot background where the panel paints none. Where a layer maps its outline
colour to the same groups as its fill, each group counts the better of the
two, since WCAG lets a border carry a graphical object's contrast. For a
translucent colour, the detail names the colour, its alpha and the
colour it is drawn as.
redundant_encoding counts how many of shape and line type also vary
across the layers that draw the encoding colours. They are gathered as the
colours are. A layer contributes the values it maps to a discrete scale, or
the two or more it draws, and a shape or line type set as a constant is
left out. Zero means the distinction between groups is carried by colour
alone, the single most common way an otherwise careful figure becomes
unreadable.
annotation_text_size is the smallest point size of the text drawn
inside the panel, scaled as text_size is and held to the same
min_text_pt. That text comes from layers such as geom_text and
geom_label, from annotations, and from depictr's own cell values and
labels. Its contrast is not measured, since it sits on the marks rather
than on one background.
text_size_max is reported only when max_text_pt is given, as the
last row. It is the largest point size of all the text measured, around
the panel and inside it, scaled as text_size is. It is the one row
that passes at or below its threshold.
Limitations of the default palette. The eight-colour qualitative
palette clears the colour-separability checks comfortably and fails
greyscale_separability: its orange (#e69f00) and sky blue
(#56b4e9) differ by only 0.79 in lightness, so they print as the same
grey. The colourblind-safety guarantee depictr makes is about hue confusion,
and it was never a claim about greyscale. A figure that may be printed in
black and white should use fewer groups, or a sequential palette, or add a
redundant shape or line type; the four leading colours are not safe in
greyscale either, since the bluish green and the vermillion differ by 3.55.
The threshold has been left where it is rather than moved to let the
package's own defaults through.
The palette falls short on contrast as well. Four of its eight colours are
below the 3:1 against white that WCAG asks of a graphical object: the
orange at 2.25:1, the sky blue at 2.31:1, the grey at 2.85:1 and the yellow
at 1.32:1. The orange is the second colour, so a default figure on white
whose colour tells two or more groups apart fails geometry_contrast,
and drawing its marks translucent lowers the ratio further. Opaque marks,
the vermillion (#d55e00, at 3.87:1) in the orange's place and a
redundant shape clear that row and redundant_encoding, as the first
example below shows. The contrast-first order of
:func:depictr.depictr_palette puts the vermillion second and the reddish
purple third, and :func:depictr.scale_colour_depictr takes it as
order="contrast". Up to three groups then pass every colour row, and
four keep 3:1 against white.
Auditing at the printed size. Text is drawn in points, so the size it
prints at depends on the two widths. A figure drawn at the size it will be
printed, as :func:depictr.save_plot draws one given
width=8.9, units="cm", needs no scaling. Pass the saved width as both
width_cm and render_width_cm, or leave both at their defaults.
Given alone, width_cm audits the figure as though it had been drawn
seven inches wide and then shrunk to fit. :func:depictr.save_plot with
check=True audits a figure at the width it saves it at, given as both.
The default min_text_pt of 6 is lenient. As their guidelines stood on
30 September 2026, PLOS asked for text of 8 to 12 pt and APA Style for 8 to
14 pt. Elsevier asked for 7 pt as a rule of thumb, and Nature for 5 to 7
pt, with panel labels at 8 pt. Pass the floor of the journal the figure is
for as min_text_pt, and its ceiling, where it sets one, as
max_text_pt. Nature's 7 pt applies to all text but the panel labels,
and a panel label drawn as a panel's title or annotation is measured with
the rest. The four pages are listed under References.
Compositions. A composition, such as :func:depictr.arrange_plots,
:func:depictr.residual_diagnostics_plot and :func:depictr.model_report
return, is audited panel by panel. Each panel has its own scales, theme
and text, so each is audited as it would be on its own. The reports are
stacked under a leading integer column, panel, which only the report
of a composition has. The widths are those of the whole composition.
A panel's text keeps its point size whatever share of the width the panel
takes, so the text of every panel scales by the same ratio.
Panels are numbered depth-first through any composition nested inside, in
the order the composition holds them, which is the reading order of the
grids :func:depictr.arrange_plots builds. A plot_spacer() draws no
figure of its own and takes no number. A panel's legend is measured with
the panel. Only the panels are measured, so anything a composition draws
around them is not. The R twin numbers the panels of a patchwork by the
same rule. The panel tags :func:depictr.arrange_plots adds belong to the
panels' own plots in this twin, so each tag is measured with its panel,
by text_size_max too. The R twin's patchwork draws its tags around the
panels, where they are not measured, so a composition arranged with
tag_levels=None is the one whose report matches the R twin's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plot
|
ggplot or Compose
|
A plot, as returned by any depictr plotting function, including one
extended afterwards with |
required |
width_cm
|
float
|
The width, in centimetres, that the figure will occupy in the finished
document. Defaults to 17.78 cm, the seven inches
:func: |
17.78
|
render_width_cm
|
float
|
The width, in centimetres, that the figure is drawn at. Defaults to the same 17.78 cm. The ratio of the two widths is the factor every point size is multiplied by. |
17.78
|
min_delta_e
|
float
|
The smallest acceptable colour difference, on the scale of |
5
|
min_text_pt
|
float
|
The smallest acceptable printed text size, in points, for the text around the panel and the text inside it alike. Defaults to 6, below the floor most journals set (see "Auditing at the printed size" above). |
6
|
metric
|
('ciede2000', 'cie76')
|
The colour difference the colour separability checks measure:
CIEDE2000, the default, or the Euclidean distance in CIELAB that
earlier versions measured. |
"ciede2000"
|
max_text_pt
|
float
|
The largest acceptable printed text size, in points, or None, the
default, for no ceiling. When given, a |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per check, with columns |
See Also
depictr.palette_safety : the palette in the abstract, rather than a figure.
References
APA Style. Figures. https://apastyle.apa.org/style-grammar-guidelines/tables-figures/figures
Crameri, F., Shephard, G. E., & Heron, P. J. (2020). The misuse of colour in science communication. Nature Communications, 11(1), 5444. https://doi.org/10.1038/s41467-020-19160-7
Elsevier. Artwork sizing. https://www.elsevier.com/about/policies-and-standards/author/artwork-and-media-instructions/artwork-sizing
Nature. Guide to preparing final artwork. https://www.nature.com/documents/nature-final-artwork.pdf
PLOS ONE. Figures. https://journals.plos.org/plosone/s/figures
Sharma, G., Wu, W., & Dalal, E. N. (2005). The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations. Color Research & Application, 30(1), 21-30. https://doi.org/10.1002/col.20070
Examples:
>>> import depictr as dp
>>> from plotnine import aes, geom_point, ggplot, scale_colour_manual
>>> cy = dp.crop_yield()
>>> good = (ggplot(cy, aes("rainfall", "yield", colour="treatment",
... shape="treatment"))
... + geom_point()
... + scale_colour_manual(values=["#005b96", "#d55e00"])
... + dp.theme_depictr())
Every check passes but the one for text inside the panel, since the figure draws none there.
>>> report = dp.check_figure(good)
>>> report.loc[report["verdict"] != "pass", "check"].tolist()
['annotation_text_size']
The same figure destined for an 8.9 cm journal column, where the text is half the size it looks on screen.
>>> shrunk = dp.check_figure(good, width_cm=8.9)
>>> shrunk.loc[shrunk["check"] == "text_size", "verdict"].item()
'fail'
Saved at 8.9 cm in the first place, with save_plot(width=8.9,
units="cm"), it is drawn at the size it prints, so both widths are 8.9.
>>> saved = dp.check_figure(good, width_cm=8.9, render_width_cm=8.9)
>>> saved.loc[saved["check"] == "text_size", "verdict"].item()
'pass'
Nature allowed no text above 7 pt but the panel labels. The axis titles here are 11 pt, so the ceiling row fails.
>>> nature = dp.check_figure(good, max_text_pt=7)
>>> nature.loc[nature["check"] == "text_size_max",
... ["measured", "verdict"]].values.tolist()
[[11.0, 'fail']]
Each panel of a composition is audited, and a column says which panel each row describes. At base size 5, the second panel's text is too small.
>>> small = good + dp.theme_depictr(base_size=5)
>>> both = dp.check_figure(dp.arrange_plots(good, small))
>>> both.loc[both["verdict"] == "fail", ["panel", "check"]].values.tolist()
[[2, 'text_size']]
Source code in src/depictr/audit.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | |
palette_safety ¶
palette_safety(colours=None, threshold=5.0, metric='ciede2000')
Check that a palette stays distinguishable under each deficiency.
For normal vision and each deficiency at full severity, the palette's
colours are converted to CIELAB and the smallest colour difference between
any two of them is found. The difference is CIEDE2000 (Sharma, Wu & Dalal,
2005) unless metric asks for the older measure. The lower this minimum,
the more likely two categories are to be confused. A palette is reported
safe when the minimum across all conditions is at least threshold.
Earlier versions measured the Euclidean distance in CIELAB, known as CIE76,
which overstates the difference between saturated colours: the blues
#005b96 and #0060a8 are 8.20 apart by CIE76 and 2.69 by CIEDE2000.
ggplot2's default palette of five colours passed the check under CIE76,
although under deuteranopia its sky blue and its pink come within 2.74 of
each other in CIEDE2000.
The default threshold of 5.0 is calibrated against the reference
colourblind-safe palette: the Okabe-Ito set's tightest pair (reddish-purple
against grey) sits at 6.43 under full deuteranopia, so the cut must lie
below that to pass the recommended palette, while still flagging colours that
become near-identical under a deficiency. The difference includes lightness,
which survives colour-vision deficiency, so two colours that share a hue but
differ in lightness are correctly treated as distinguishable. Full severity
is the worst case; most colour-vision deficiency is milder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colours
|
str or list of str
|
The palette to test. Defaults to the depictr qualitative palette. Needs at least two colours: a pairwise distance over fewer than two has no value, not an infinitely safe one. A single string is one colour, so it raises with that message. |
None
|
threshold
|
float
|
The smallest acceptable colour difference, on the scale of |
5.0
|
metric
|
('ciede2000', 'cie76')
|
The colour difference to measure: CIEDE2000, the default, or the
Euclidean distance in CIELAB that earlier versions measured.
|
"ciede2000"
|
Returns:
| Type | Description |
|---|---|
dict
|
|
See Also
depictr.check_figure : the same question asked of a finished figure, which uses only as many colours as it has groups and has text and a background besides.
References
Sharma, G., Wu, W., & Dalal, E. N. (2005). The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations. Color Research & Application, 30(1), 21-30. https://doi.org/10.1002/col.20070
Examples:
>>> import depictr as dp
>>> dp.palette_safety()['safe']
True
ggplot2's default palette of five colours, which passed under CIE76.
>>> hue5 = ["#F8766D", "#A3A500", "#00BF7D", "#00B0F6", "#E76BF3"]
>>> dp.palette_safety(hue5)['safe'], dp.palette_safety(hue5, metric="cie76")['safe']
(False, True)
Source code in src/depictr/cvd.py
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 | |
simulate_cvd ¶
simulate_cvd(colours, deficiency, severity=1.0)
Simulate how a palette appears under a colour-vision deficiency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colours
|
str or list of str
|
Colours (any matplotlib-readable form) to transform. A single string is one colour, and an empty list gives an empty list back, as in the R twin. |
required |
deficiency
|
('protan', 'deutan', 'tritan')
|
The deficiency to simulate (red-, green- or blue-weak vision). |
"protan"
|
severity
|
float
|
Severity in [0, 1]. Zero leaves the colours unchanged and one is the full deficiency. Machado et al. publish a matrix for each tenth of severity, which is used as it stands at 0.1, 0.2 and so on. A severity between two tenths takes the linear interpolation of their matrices, as the authors suggest and as colorspacious does. |
1.0
|
Returns:
| Type | Description |
|---|---|
list of str
|
The simulated colours as hex strings. |
Examples:
>>> import depictr as dp
>>> dp.simulate_cvd(['#005b96', '#e69f00'], 'deutan')
['#275295', '#cab411']
See Also
depictr.simulate_figure : the same simulation applied to a whole figure.
Source code in src/depictr/cvd.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
simulate_figure ¶
simulate_figure(plot, deficiency, severity=1.0, dpi=150)
Show a finished figure as a reader with a colour-vision deficiency sees it.
:func:simulate_cvd simulates a list of colours, and this function the
figure they end up in, legend, colourbar, text and background included. A
copy of the plot is drawn and rasterised with matplotlib's Agg renderer.
Every pixel then goes through the same transform: it is decoded from sRGB
to linear light, multiplied by Machado et al.'s matrix for the deficiency
and severity and encoded again. The alpha channel is kept as drawn.
The R twin's simulate_figure() applies the same matrices to the
figure's vector elements and returns a gtable to draw or save. A solid
region comes out in the same colour in both, to within the rounding of an
eight-bit channel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plot
|
ggplot or Compose
|
The plot, or a composition of several, as it would be saved. It is
left as it was, since a copy is drawn. A plot whose |
required |
deficiency
|
('protan', 'deutan', 'tritan')
|
The deficiency to simulate (red-, green- or blue-weak vision). |
"protan"
|
severity
|
float
|
Severity in [0, 1], as for :func: |
1.0
|
dpi
|
float
|
The resolution to rasterise at, in dots per inch. The image is the
figure's width and height in inches times |
150
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The image as floats in [0, 1], of shape (height, width, 4), with the
channels red, green, blue and alpha. Matplotlib's |
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
ValueError
|
When |
Examples:
>>> import depictr as dp
>>> p = dp.ecdf_plot(dp.lexical_decision(), "RT", group="condition")
>>> seen = dp.simulate_figure(p, "deutan", dpi=72)
>>> seen.shape[2], bool(seen.min() >= 0 and seen.max() <= 1)
(4, True)
Under normal vision, for a side-by-side comparison:
>>> as_drawn = dp.simulate_figure(p, "deutan", severity=0, dpi=72)
>>> as_drawn.shape == seen.shape
True
Source code in src/depictr/cvd.py
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | |
format_terms ¶
format_terms(x, interaction='times', strip_prefix=True, tidy_intercept=True, wrap=None)
Tidy raw coefficient names for display.
Cleans up the term names that modelling functions produce so that they read
well on a plot, following the rules of the R twin's format_terms(). The
intercept is renamed Intercept, a b_ or bs_ prefix (as brms adds)
is stripped from each component of an interaction, interaction colons are
written with a chosen symbol, and underscores are shown as spaces, so
word_frequency becomes word frequency.
The names patsy and formulaic give a formula's terms are read as well. A
factor level becomes variable: level, as treatment[T.enhanced] and
C(treatment)[T.enhanced] become treatment: enhanced, and a
Q("...") wrapper is dropped, so Q("region")[T.North] becomes
region: North. A colon inside a level, a call or a quoted name does not
count as an interaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
str or iterable of str
|
The term names. Missing values ( |
required |
interaction
|
('times', 'asterisk', 'colon', 'space')
|
How to write an interaction: a multiplication sign (the default), an asterisk, the colon unchanged or a space. |
"times"
|
strip_prefix
|
bool
|
Whether to remove a leading |
True
|
tidy_intercept
|
bool
|
Whether to replace |
True
|
wrap
|
int
|
Width at which to wrap long labels onto new lines, each line kept
shorter than |
None
|
Returns:
| Type | Description |
|---|---|
str or list
|
A string for a string, otherwise a list as long as |
Examples:
>>> import depictr as dp
>>> dp.format_terms(["Intercept", "treatment[T.enhanced]", "soil_ph"])
['Intercept', 'treatment: enhanced', 'soil ph']
>>> dp.format_terms("rainfall:treatment[T.enhanced]", interaction="asterisk")
'rainfall * treatment: enhanced'
>>> dp.format_terms('Q("region")[T.North]')
'region: North'
Source code in src/depictr/terms.py
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |
Composition and data¶
Compose several plots into one figure or save one to disk, and the datasets used throughout the documentation. These are the R package's own synthetic datasets, shipped as files, so an example gives the same numbers in both packages.
The datasets are used throughout the gallery, where every figure is drawn from one of them.
arrange_plots ¶
arrange_plots(*plots, ncol=None, nrow=None, title=None, tag_levels='A', tag_size=None)
Arrange several plots into one composed figure.
Builds a grid by joining plots side by side (plotnine's |) into rows of
ncol, then stacking the rows (/). The result is a plotnine
composition, so it still has .draw and .save. This is the building
block behind the multi-panel reports (for example
:func:depictr.residual_diagnostics_plot).
Each panel is tagged A, B, C and so on by default, in reading order, as
the R twin's patchwork annotation tags them. The tags run on through a
composition passed as one of the plots, and a plot_spacer() takes
none. A tag is drawn in the top-left corner of its panel at the theme's
plot_tag size, 1.2 times the base size, as in ggplot2. In this twin
each tag belongs to its panel's plot, so :func:depictr.check_figure
measures it with the rest of the panel's text, where the R twin's audit
leaves out the tags that patchwork draws around the panels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*plots
|
ggplot or Compose
|
The plots to arrange. |
()
|
ncol
|
int
|
The grid shape. Give one and the other is derived from the count; give neither and a sensible default is chosen (one column for a single plot, two up to four plots, three beyond). |
None
|
nrow
|
int
|
The grid shape. Give one and the other is derived from the count; give neither and a sensible default is chosen (one column for a single plot, two up to four plots, three beyond). |
None
|
title
|
str
|
A title. Applied only when a single plot is passed: plotnine compositions cannot carry a figure-level title, so for a grid the title is dropped with a warning, and each panel should carry its own. |
None
|
tag_levels
|
('A', 'a', '1', 'I', 'i')
|
How to tag the panels: capital or lower-case letters, Arabic numerals,
or capital or lower-case Roman numerals, as patchwork's
|
"A"
|
tag_size
|
float
|
The size of the tags in points, such as the 8 pt that Nature asks of
panel labels. |
None
|
Returns:
| Type | Description |
|---|---|
ggplot or Compose
|
A single plot when one is passed, otherwise a composition. The plots passed in are left untagged. |
Examples:
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> left = dp.ecdf_plot(ld, "RT")
>>> right = dp.explore_distribution(ld, "RT")
>>> panel = dp.arrange_plots(left, right, ncol=2)
Lower-case tags at 8 pt, or none at all:
>>> panel = dp.arrange_plots(left, right, tag_levels="a", tag_size=8)
>>> panel = dp.arrange_plots(left, right, tag_levels=None)
Source code in src/depictr/compose.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
save_plot ¶
save_plot(plot, filename, width=7, height=4.5, dpi=300, units='in', check=False, **kwargs)
Save a depictr/plotnine plot at publication resolution.
Missing directories on the way to filename are created, and PDF, EPS
and TIFF files are written to pass the technical checks journals run on
submitted figures. With check=True, the figure is also audited with
:func:depictr.check_figure at the width it is saved at.
A PDF or EPS embeds its fonts as TrueType (Type 42). matplotlib's default, Type 3, is the kind Nature refuses, and the setting holds for this save only. PostScript has no transparency, so matplotlib draws translucent layers opaque in an EPS, where they hide what lies beneath them, and a warning says how many layers that affects. A PDF keeps them as they are.
A TIFF is written as LZW-compressed RGB at dpi, with any transparent
background composited onto white. matplotlib alone writes uncompressed
RGBA, which PLOS refuses on both counts: the figure in the example below
takes 10.8 MiB that way. Passing pil_kwargs leaves the TIFF to
matplotlib as it was.
Which formats three publishers accepted on 2 October 2026:
| Format | PLOS | Nature (final artwork) | Elsevier |
|---|---|---|---|
| No | Yes, as vector art | Yes | |
| EPS | Yes, with fonts embedded | Yes, as vector art | Yes, with fonts embedded |
| TIFF | Yes, LZW-compressed RGB or greyscale, no alpha channel, at most 10 MB | No | Yes |
Nature also asks for fonts embedded as TrueType 2 or 42, never Type 3, and
for line art and text that are not rasterised. A figure with translucent
layers therefore goes to Nature as a PDF. Elsevier asks for a graph sent as
a TIFF at 1000 dpi, so pass dpi=1000 for one.
The R twin takes the file name first, as save_plot(filename, plot). For
an EPS, it rasterises the translucent layers and whatever they overlap,
where matplotlib draws them opaque.
PLOS ONE. Figures. https://journals.plos.org/plosone/s/figures
Nature. Guide to preparing final artwork. https://www.nature.com/documents/nature-final-artwork.pdf
Nature research figure guide. Preparing figures: our specifications. https://research-figure-guide.nature.com/figures/preparing-figures-our-specifications/
Elsevier. Artwork overview. https://www.elsevier.com/about/policies-and-standards/author/artwork-and-media-instructions/artwork-overview
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plot
|
ggplot or Compose
|
The plot to save, or a composition of several. |
required |
filename
|
str
|
Output path; the extension sets the format ( |
required |
width
|
float
|
Figure size, in |
7
|
height
|
float
|
Figure size, in |
7
|
dpi
|
int
|
Dots per inch (300 suits print). |
300
|
units
|
str
|
Size units ( |
'in'
|
check
|
bool
|
Whether to audit the figure with :func: |
False
|
**kwargs
|
Passed to :meth: |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The filename written. With |
Warns:
| Type | Description |
|---|---|
UserWarning
|
When an EPS or PostScript file is saved from a plot with translucent
layers, and, with |
Examples:
>>> import os, tempfile
>>> import depictr as dp
>>> ld = dp.lexical_decision()
>>> p = dp.ecdf_plot(ld, "RT")
>>> path = dp.save_plot(p, os.path.join(tempfile.mkdtemp(), "rt.png"),
... width=4, height=3, dpi=72)
>>> os.path.exists(path)
True
A TIFF well within the 10 MB that PLOS allows, in a folder that does not yet exist.
>>> tiff = os.path.join(tempfile.mkdtemp(), "figures", "rt.tiff")
>>> path = dp.save_plot(p, tiff)
>>> os.path.getsize(path) < 1_000_000
True
Audited at the 8.9 cm column it is saved for, where its text prints at the size it is drawn. It passes every check that applies, so nothing is said.
>>> column = os.path.join(tempfile.mkdtemp(), "rt-column.png")
>>> path = dp.save_plot(p, column, width=8.9, height=6, units="cm",
... dpi=72, check=True)
Source code in src/depictr/compose.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 | |
crop_yield ¶
crop_yield(seed=None)
A field trial in which the fertiliser effect depends on the treatment.
200 fields: field (identifier), treatment (standard, enhanced),
rainfall (mm), fertiliser (kg/ha), soil_ph and yield
(t/ha). The R package's crop_yield.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
None
|
Deprecated and ignored. Passing a value raises a |
None
|
Examples:
>>> import depictr as dp
>>> dp.crop_yield().shape
(200, 6)
Source code in src/depictr/data.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | |
wellbeing_survey ¶
wellbeing_survey(seed=None)
A cross-sectional survey with regional contrasts and informative missingness.
300 respondents: id, region (East, North, South, West), age,
education (ordered: secondary < undergraduate < postgraduate),
income, stress, sleep_hours, exercise_days and
life_satisfaction, with values missing in income, sleep hours, exercise
days and life satisfaction. The R package's wellbeing_survey.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
None
|
Deprecated and ignored. Passing a value raises a |
None
|
Examples:
>>> import depictr as dp
>>> dp.wellbeing_survey().shape
(300, 9)
Source code in src/depictr/data.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
lexical_decision ¶
lexical_decision(seed=None)
A priming experiment with crossed participants and items.
960 trials: 24 participant levels crossed with 40 item levels,
condition (related, unrelated), modality (visual, auditory),
word_frequency, RT (ms) and accuracy (0/1). The R package's
lexical_decision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
None
|
Deprecated and ignored. Passing a value raises a |
None
|
Examples:
>>> import depictr as dp
>>> dp.lexical_decision().shape
(960, 7)
Source code in src/depictr/data.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
clinical_trial ¶
clinical_trial(seed=None)
A two-arm trial with separating survival curves and a rare adverse event.
300 patients: patient (identifier), arm (placebo, treatment),
age, biomarker, follow-up time (months), event (1 = event,
0 = censored) and adverse_event (0/1). The R package's
clinical_trial.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
None
|
Deprecated and ignored. Passing a value raises a |
None
|
Examples:
>>> import depictr as dp
>>> dp.clinical_trial().shape
(300, 7)
Source code in src/depictr/data.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
monthly_sales ¶
monthly_sales(seed=None)
Two monthly sales series with a trend and a yearly season.
144 rows in long format: date (the first of each month, 2018 to 2023),
series (indoor, outdoor) and sales. The R package's
monthly_sales. It is not in :data:DATASETS, which lists the
cross-sectional datasets the web app offers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
None
|
Deprecated and ignored. Passing a value raises a |
None
|
Examples:
>>> import depictr as dp
>>> dp.monthly_sales().shape
(144, 3)
Source code in src/depictr/data.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
DATASETS
module-attribute
¶
DATASETS = {'crop_yield': crop_yield, 'wellbeing_survey': wellbeing_survey, 'lexical_decision': lexical_decision, 'clinical_trial': clinical_trial}