Introduction
"Did this get better or worse, and by how much, for each category" — pre/post, treatment/control, this year vs. last — is a change question, and a dumbbell chart answers it more directly than two separate bar charts ever could. PlotDotPlot draws one dot per group per category, connects them with a line whose length and direction show the magnitude and sign of the change, and can sort categories by that delta so the biggest movers are the easiest to spot.
Teaching Note
Dot plots (Dumbbell charts) are essential for:
- Epidemiology: Comparing disease prevalence rates before and after a public health intervention.
- Healthcare: Visualizing patient symptom scores before and after a specific clinical treatment.
- Data Science: Comparing model accuracy metrics across two different algorithm configurations.
- Public Health: Monitoring the shift in regional particulate matter levels over a 10-year period.
- Marketing: Analyzing the lift in customer engagement for treatment vs. control groups in an A/B test.
- Economics: Visualizing the change in unemployment rates across multiple districts during a recession.
- Quality Control: Comparing defect rates between two different manufacturing facilities or shifts.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
dataframerequired | pd.DataFrame | — | The pandas DataFrame containing the categorical, numeric, and group columns. |
categorical_column_namerequired | str | — | The name of the column representing the categories (y-axis). |
value_column_namerequired | str | — | The name of the column representing the numeric values (x-axis). |
group_column_namerequired | str | — | The name of the categorical column with exactly two unique values (e.g., 'Year', 'Group'). |
group_1_color | str | '#4257f5' | The hex color code for the first group's dots. Defaults to '#4257f5'. |
group_2_color | str | '#ccd2ff' | The hex color code for the second group's dots. Defaults to '#ccd2ff'. |
dot_size | int | 20 | The size of the marker dots. Defaults to 20. |
dot_alpha | float | 1 | The transparency level (alpha) of the marker dots (0 to 1). Defaults to 1. |
zero_line_group | str | None | The name of a group to treat as the baseline (zero-point). If provided, values are expressed as deltas from this group. Defaults to None. |
connect_dots | bool | True | Whether to draw a connecting line between the two dots in each category. Defaults to True. |
connect_line_color | str | '#666666' | The hex color code for the connecting lines. Defaults to '#666666'. |
connect_line_alpha | float | 0.4 | The transparency of the connecting lines. Defaults to 0.4. |
connect_line_width | float | 1.0 | The thickness of the connecting lines. Defaults to 1.0. |
connect_line_style | str | 'dashed' | Matplotlib line style for the connecting lines (e.g., 'solid', 'dashed'). Defaults to 'dashed'. |
dict_of_connect_line_labels | dict | None | A dictionary mapping category names to custom text labels for their connecting lines. Defaults to None. |
connect_line_label_fontsize | int | 11 | The font size for the connecting line labels. Defaults to 11. |
connect_line_label_fontsize_fontweight | str | 'bold' | The font weight for the connecting line labels (e.g., 'bold', 'normal'). Defaults to 'bold'. |
connect_line_label_color | str | '#262626' | The hex color code for the connecting line labels. Defaults to '#262626'. |
show_connect_line_labels_in_margin | bool | False | Whether to display connecting line labels in the right margin instead of centered on the line. Defaults to False. |
connect_line_label_margin | float | 0.5 | The horizontal offset for labels when show_connect_line_labels_in_margin is True. Defaults to 0.5. |
display_order_list | list | None | A specific list of categories for the y-axis. If None, categories are sorted by the absolute difference between groups. Defaults to None. |
figure_size | tuple | (10, 6) | Dimensions of the output figure as a (width, height) tuple in inches. Defaults to (10, 6). |
show_x_axis | bool | False | Whether to display the x-axis tick marks and labels. Defaults to False. |
show_data_labels | bool | True | Whether to display numeric text labels for the value dots. Defaults to True. |
decimal_places_for_data_label | int | 1 | Number of decimal places for numeric data labels. Defaults to 1. |
data_label_fontsize | int | 11 | The font size for the numeric text labels. Defaults to 11. |
data_label_fontweight | str | 'bold' | The font weight for the numeric text labels. Defaults to 'bold'. |
data_label_color | str | '#262626' | The hex color code for the numeric text labels. Defaults to '#262626'. |
title_for_plot | str | None | The primary title text at the top of the chart. Defaults to None. |
subtitle_for_plot | str | None | Descriptive subtitle text below the main title. Defaults to None. |
caption_for_plot | str | None | Explanatory text or notes at the bottom of the plot. Defaults to None. |
data_source_for_plot | str | None | Text identifying the data source, appended to the caption. Defaults to None. |
title_y_indent | float | 1.15 | Vertical offset for the title position. Defaults to 1.15. |
subtitle_y_indent | float | 1.1 | Vertical offset for the subtitle position. Defaults to 1.1. |
caption_y_indent | float | -0.15 | Vertical offset for the caption position. Defaults to -0.15. |
filepath_to_save_plot | str | None | The local path (ending in .png or .jpg) where the plot should be exported. If None, the file is not saved. Defaults to None. |
Returns
None — the function displays the dot plot using matplotlib and optionally saves it to disk.
Example
python
from analysistoolbox.visualizations import PlotDotPlot
import pandas as pd
# Epidemiology: infection rates before and after vaccination program
epi_df = pd.DataFrame({
'Province': ['North', 'South', 'East', 'West'] * 2,
'Year': ['2023', '2023', '2023', '2023', '2024', '2024', '2024', '2024'],
'Incidence': [120, 150, 95, 210, 45, 80, 55, 130]
})
PlotDotPlot(
epi_df, 'Province', 'Incidence', 'Year',
title_for_plot="Influenza Incidence Reduction",
subtitle_for_plot="Pre (2023) vs. Post (2024) vaccination campaign data",
group_1_color="#e74c3c", group_2_color="#27ae60"
)