Introduction
When a crowd ends up on its feet — or a market rallies, or a meeting reaches apparent consensus — how much of that outcome reflects independent judgment, and how much is imitation of a few visible early movers? Miller and Page's standing ovation model separates the two. Each audience member first decides on their own noisy read of the show's quality, then reacts to the people they can see; the simulation tracks who stood on their own judgment, who stood from peer pressure, and whether planted pioneers tipped the room. Comparing several show qualities side by side, or sweeping across a range of them, shows when a collective outcome is a reliable signal and when it is mostly amplification.
The standing ovation model, introduced by John Miller and Scott Page, shows that a collective outcome can say surprisingly little about what individuals actually think. Whether an audience ends up on its feet depends not only on how good the show was, but on how diverse the audience's perceptions were, how easily people give in to what they see around them, and — crucially — who can see whom. Two audiences watching the same show can end in a full ovation or polite applause purely because of where the early standers happened to sit.
The model makes the asymmetry of visibility concrete. With the default vision cone, people in the front row cannot see anyone, so they act only on their own judgment, while everyone behind them can see the front. A few people standing in the front can pull up the whole hall; the same people standing in the back are invisible. This is why "claques" (planted pioneers) sit up front, and why in organizations the opinions of highly visible people carry outsized weight.
The model also shows that diversity matters. When a show is slightly below the quality threshold, an audience that perceives it identically will stay seated. An audience with more varied tastes produces a few enthusiasts who stand, and peer pressure can cascade from them. Comparing the share who stood on their own judgment with the final share standing separates the signal (genuine approval) from the amplification (conformity).
For analysts, the lesson is to be careful about reading consensus as evidence. A unanimous-looking outcome — a crowd on its feet, a market rally, a room that agrees — may reflect a small number of visible early movers and a cascade of imitation rather than many independent judgments. Ask who could see whom, and who moved first.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
qualities | sequence of float | (0.4, 0.5, 0.6, 0.7) | The true show quality values to compare. Each quality gets its own column in the plot, and every column uses the same audience. Defaults to (0.4, 0.5, 0.6, 0.7). |
quality_threshold | float | 0.6 | How good the show has to seem before an agent stands on their own judgment. Defaults to 0.6. |
noise | float | 0.1 | The standard deviation of each agent's perception error. Higher values mean more diversity of taste in the audience. Must be at least 0. Defaults to 0.1. |
peer_threshold | float | 0.5 | The share of visible people who must be standing before a seated agent stands, between 0 and 1. 0 means an agent stands if anyone they see stands; 1 means agents never give in to peer pressure. Defaults to 0.5. |
peer_spread | float | 0.0 | Each agent's peer threshold is drawn uniformly from peer_threshold ± peer_spread (clipped to 0 to 1), adding diversity of conformity. Must be at least 0. Defaults to 0.0. |
can_sit_back_down | bool | False | Whether a standing agent (other than a pioneer) sits down again when more than their peer threshold of the people they see are seated. Defaults to False. |
hall_shape | tuple of int | (20, 40) | The shape of the hall as (rows, seats per row). Row 0 is the front. Defaults to (20, 40). |
neighborhood | str | 'cone' | Which seats an agent can see: 'cone' (the cone_depth rows ahead, widening by one seat on each side per row — Miller and Page's vision cone), 'row_ahead' (only the 3 seats directly in front), or 'moore' (the 8 surrounding seats, including people behind). With 'cone' or 'row_ahead', the front row sees nobody and only follows its own judgment. Defaults to 'cone'. |
cone_depth | int | 3 | How many rows ahead an agent can see when neighborhood is 'cone'. Defaults to 3. |
number_of_pioneers | int | 0 | The number of agents who stand no matter what. Defaults to 0. |
pioneer_placement | str | 'front' | Where pioneers sit: 'front', 'middle', or 'back' rows (filled from the center seat of each row outward), or 'random'. Defaults to 'front'. |
max_steps | int | 50 | The maximum number of peer pressure steps before stopping. Defaults to 50. |
ovation_level | float | 0.8 | The final share standing (greater than 0, up to 1) that counts as a standing ovation in the summary table. Defaults to 0.8. |
random_seed | int | 412 | The seed for the random number generator to ensure replicability. Defaults to 412. |
print_step_by_step | bool | False | Whether to print a text view of the hall at every step for each quality (. seated, S own judgment, p peer pressure, * pioneer). Best for small halls. Defaults to False. |
include_quality_sweep | bool | False | Whether to also run the model across a fine range of qualities (over several seeds) and add a panel showing the final share standing versus quality, compared with the share who stood on their own judgment. Defaults to False. |
sweep_qualities | sequence of float | None | The qualities used in the sweep. If None, uses 21 evenly spaced values from quality_threshold - 0.3 to quality_threshold + 0.2. Defaults to None. |
sweep_number_of_seeds | int | 10 | The number of random audiences averaged at each sweep quality. Defaults to 10. |
return_format | str | 'dataframe' | The format of the returned data: 'dataframe' (summary table only) or 'dict' (summary table plus every step of every hall and sweep results). Defaults to 'dataframe'. |
plot_simulation_results | bool | True | Whether to display the combined figure of halls and charts. Defaults to True. |
state_colors | sequence of str | None | Four hex color codes for seated agents, agents who stood on their own judgment, agents who stood from peer pressure, and pioneers. If None, uses ('#E6E6E6', '#3F7FBF', '#E69F00', '#D55E00'). Defaults to None. |
show_cell_borders | bool | True | Whether to draw faint lines between seats. Defaults to True. |
figure_size | tuple | None | The size of the figure in inches (width, height). If None, it is sized automatically from the number of qualities. Defaults to None. |
title_for_plot | str | 'Standing Ovation Model' | The main title for the figure. Defaults to 'Standing Ovation Model'. |
subtitle_for_plot | str | 'How private judgment and peer pressure combine to decide whether an audience stands' | The descriptive subtitle for the figure. |
caption_for_plot | str | None | Optional caption text displayed at the bottom of the figure. Defaults to None. |
data_source_for_plot | str | None | Optional data source identification text. Defaults to None. |
Returns
With return_format='dataframe', a summary table with one row per quality and the columns Quality, Converged, Steps, Initial Share Standing, Final Share Standing, Share Stood On Own Judgment, Share Stood From Peer Pressure, Share Pioneers, and Ovation — the three Share Stood columns break down the final hall and sum to Final Share Standing. With return_format='dict', a dictionary holding that summary, per-quality results (every step's hall frames, the share_history, and the agents' private signals), the peer_thresholds and pioneers arrays, and a sweep DataFrame (or None if no sweep was run).
Example
from analysistoolbox.simulations import SimulateStandingOvationModel
# Marketing: compare four show qualities in a 20 x 30 hall
summary = SimulateStandingOvationModel(
qualities=(0.4, 0.5, 0.6, 0.7),
hall_shape=(20, 30)
)
print(summary[['Quality', 'Share Stood On Own Judgment', 'Final Share Standing', 'Ovation']])
# Strategic seating: can 15 pioneers in the front rows rescue a mediocre show?
results = SimulateStandingOvationModel(
qualities=(0.4,),
number_of_pioneers=15,
pioneer_placement='front',
return_format='dict'
)