How to Calculate Expected Value
Expected value is the single most important concept in probability and decision theory. It tells you what outcome to expect on average if you could repeat a scenario infinitely. More practically,...
Key Insights
- Expected value is the probability-weighted average of all possible outcomes, calculated as E(X) = Σ(xᵢ × P(xᵢ)), giving you a single number that represents the long-run average result
- The formula applies universally across discrete scenarios (dice, business decisions) and continuous distributions, but expected value alone doesn’t tell you about risk—you need variance and standard deviation for that
- In practice, expected value drives decisions in finance, insurance, product development, and A/B testing, but it only becomes reliable over many iterations due to the law of large numbers
Introduction to Expected Value
Expected value is the single most important concept in probability and decision theory. It tells you what outcome to expect on average if you could repeat a scenario infinitely. More practically, it’s the tool that lets you compare options with uncertain outcomes on equal footing.
The mathematical definition is straightforward: multiply each possible outcome by its probability, then sum everything up. If you’re deciding whether to launch a product, play a casino game, or run an A/B test, expected value gives you the rational baseline for your decision.
Here’s the basic implementation:
def expected_value(outcomes, probabilities):
"""
Calculate expected value given outcomes and their probabilities.
Args:
outcomes: List of possible numerical outcomes
probabilities: List of probabilities (must sum to 1.0)
Returns:
Expected value as a float
"""
if len(outcomes) != len(probabilities):
raise ValueError("Outcomes and probabilities must have same length")
if not abs(sum(probabilities) - 1.0) < 1e-10:
raise ValueError(f"Probabilities must sum to 1.0, got {sum(probabilities)}")
return sum(x * p for x, p in zip(outcomes, probabilities))
# Example: Fair coin flip where heads = $1, tails = -$1
ev = expected_value([1, -1], [0.5, 0.5])
print(f"Expected value: ${ev}") # Output: $0.0
This function is deceptively simple but handles the core calculation you’ll use everywhere.
The Mathematical Foundation
The formula E(X) = Σ(xᵢ × P(xᵢ)) breaks down into three components:
- E(X): The expected value we’re calculating
- xᵢ: Each possible outcome value
- P(xᵢ): The probability of that outcome occurring
For discrete probability distributions (countable outcomes), you sum across all possibilities. For continuous distributions, you integrate instead of sum, but the concept remains identical: weight each value by its likelihood.
Let’s walk through a six-sided die to see the summation in action:
def dice_expected_value():
"""Calculate expected value of a fair six-sided die roll."""
outcomes = [1, 2, 3, 4, 5, 6]
probability_each = 1/6
# Show the step-by-step calculation
total = 0
print("Calculating expected value step by step:")
for outcome in outcomes:
contribution = outcome * probability_each
total += contribution
print(f" {outcome} × {probability_each:.4f} = {contribution:.4f}")
print(f"\nExpected value: {total:.4f}")
return total
dice_expected_value()
# Output: 3.5000
The die’s expected value of 3.5 makes intuitive sense—it’s the midpoint between 1 and 6. You’ll never actually roll a 3.5, which highlights an important point: expected value represents the long-run average, not a guaranteed outcome.
Discrete Probability Scenarios
Most real-world decisions involve discrete outcomes. You either win or lose the contract. The product succeeds or fails. The user clicks or doesn’t click.
Here’s a casino game analyzer that shows expected value in action:
def analyze_casino_game(bet_amount, win_payout, win_probability):
"""
Analyze expected value of a casino game.
Args:
bet_amount: How much you wager
win_payout: Total amount returned if you win (includes original bet)
win_probability: Probability of winning (0 to 1)
Returns:
Dictionary with analysis results
"""
lose_probability = 1 - win_probability
# Outcomes: net gain if win, net loss if lose
win_outcome = win_payout - bet_amount
lose_outcome = -bet_amount
ev = (win_outcome * win_probability) + (lose_outcome * lose_probability)
return {
'expected_value': ev,
'expected_return_pct': (ev / bet_amount) * 100,
'house_edge': -ev / bet_amount if ev < 0 else 0
}
# Roulette: Bet $10 on red (pays even money, 18/38 chance on American wheel)
roulette = analyze_casino_game(bet_amount=10, win_payout=20, win_probability=18/38)
print(f"Roulette EV: ${roulette['expected_value']:.2f}")
print(f"House edge: {roulette['house_edge']*100:.2f}%")
# Output: EV: -$0.53, House edge: 5.26%
For investment scenarios with multiple outcomes:
def investment_expected_value(investment, scenarios):
"""
Calculate expected value for investment with multiple scenarios.
Args:
investment: Initial investment amount
scenarios: List of tuples (return_multiple, probability)
Returns:
Expected profit/loss
"""
outcomes = [(investment * multiple - investment) for multiple, _ in scenarios]
probabilities = [prob for _, prob in scenarios]
return expected_value(outcomes, probabilities)
# Startup investment: $50k
# 10% chance of 10x return, 30% chance of 2x, 60% chance of total loss
scenarios = [
(10.0, 0.10), # 10x return
(2.0, 0.30), # 2x return
(0.0, 0.60) # Total loss
]
ev = investment_expected_value(50000, scenarios)
print(f"Expected profit: ${ev:,.2f}") # Output: $65,000
Real-World Applications
Expected value shines in business contexts where you need to choose between uncertain options.
For A/B testing decisions:
class ABTestDecision:
def __init__(self, control_revenue, variant_revenue,
variant_probability, implementation_cost):
self.control_revenue = control_revenue
self.variant_revenue = variant_revenue
self.variant_probability = variant_probability
self.implementation_cost = implementation_cost
def expected_value_of_shipping(self):
"""Calculate EV of shipping the variant vs. staying with control."""
# If we ship: probability of success × uplift - implementation cost
uplift = self.variant_revenue - self.control_revenue
ev_ship = (self.variant_probability * uplift) - self.implementation_cost
# If we don't ship: EV is 0 (stay with current state)
ev_no_ship = 0
return {
'ev_ship': ev_ship,
'ev_no_ship': ev_no_ship,
'recommendation': 'Ship' if ev_ship > ev_no_ship else 'Do not ship',
'expected_gain': max(ev_ship, ev_no_ship)
}
# Test showed variant might increase revenue by $100k/year
# But we're only 70% confident, and implementation costs $20k
test = ABTestDecision(
control_revenue=500000,
variant_revenue=600000,
variant_probability=0.70,
implementation_cost=20000
)
result = test.expected_value_of_shipping()
print(f"Recommendation: {result['recommendation']}")
print(f"Expected gain: ${result['expected_gain']:,.2f}")
Expected Value vs. Actual Outcomes
Expected value tells you what happens on average, but individual outcomes will vary—sometimes wildly. This is where variance and standard deviation come in.
A Monte Carlo simulation demonstrates this perfectly:
import random
import statistics
def monte_carlo_convergence(outcomes, probabilities, num_trials=10000):
"""
Simulate repeated trials to show convergence to expected value.
Returns running average at different sample sizes.
"""
theoretical_ev = expected_value(outcomes, probabilities)
# Create weighted population
population = []
for outcome, prob in zip(outcomes, probabilities):
population.extend([outcome] * int(prob * 10000))
results = []
running_sum = 0
checkpoints = [10, 100, 1000, 10000]
for i in range(1, num_trials + 1):
result = random.choice(population)
running_sum += result
running_avg = running_sum / i
if i in checkpoints:
results.append({
'trials': i,
'running_average': running_avg,
'theoretical_ev': theoretical_ev,
'difference': abs(running_avg - theoretical_ev)
})
return results
# Unfair coin: 60% heads ($1), 40% tails (-$1)
convergence = monte_carlo_convergence([1, -1], [0.6, 0.4])
print("Convergence to Expected Value:")
for r in convergence:
print(f"After {r['trials']:5d} trials: ${r['running_average']:6.3f} "
f"(EV: ${r['theoretical_ev']:.3f}, diff: ${r['difference']:.3f})")
This shows the law of large numbers in action: with more trials, your actual average converges to the expected value. But with few trials, you can see significant deviation.
Advanced Considerations
Decision trees handle sequential decisions where later choices depend on earlier outcomes:
class DecisionNode:
def __init__(self, name):
self.name = name
self.branches = []
def add_branch(self, probability, outcome_value=None, next_node=None):
"""Add a branch with probability and either a terminal value or next node."""
self.branches.append({
'probability': probability,
'outcome_value': outcome_value,
'next_node': next_node
})
def calculate_ev(self):
"""Recursively calculate expected value of this decision node."""
total_ev = 0
for branch in self.branches:
if branch['next_node']:
# Recursive case: multiply probability by EV of next node
branch_ev = branch['probability'] * branch['next_node'].calculate_ev()
else:
# Terminal case: multiply probability by outcome value
branch_ev = branch['probability'] * branch['outcome_value']
total_ev += branch_ev
return total_ev
# Example: Product development decision
# First decision: Develop product ($100k cost)
# Market research: 70% positive, 30% negative
# If positive: Launch (60% success = $500k profit, 40% failure = $0)
# If negative: Don't launch ($0)
launch_node = DecisionNode("Launch")
launch_node.add_branch(0.6, outcome_value=500000) # Success
launch_node.add_branch(0.4, outcome_value=0) # Failure
research_node = DecisionNode("Research")
research_node.add_branch(0.7, next_node=launch_node) # Positive research
research_node.add_branch(0.3, outcome_value=0) # Negative research
ev = research_node.calculate_ev() - 100000 # Subtract development cost
print(f"Expected value of product development: ${ev:,.2f}")
Practical Implementation Guide
When applying expected value analysis in real projects:
- Enumerate all meaningful outcomes - Don’t just model best/worst case
- Get probability estimates from data - Historical data beats guesses
- Run sensitivity analysis - Test how results change with different assumptions
- Communicate uncertainty - Show the range, not just the expected value
Here’s a complete implementation:
class ExpectedValueAnalysis:
def __init__(self, name):
self.name = name
self.scenarios = []
def add_scenario(self, outcome, probability, description=""):
self.scenarios.append({
'outcome': outcome,
'probability': probability,
'description': description
})
def calculate(self):
outcomes = [s['outcome'] for s in self.scenarios]
probabilities = [s['probability'] for s in self.scenarios]
ev = expected_value(outcomes, probabilities)
variance = sum(p * (x - ev)**2 for x, p in zip(outcomes, probabilities))
std_dev = variance ** 0.5
return {
'expected_value': ev,
'variance': variance,
'std_deviation': std_dev,
'min_outcome': min(outcomes),
'max_outcome': max(outcomes)
}
def report(self):
results = self.calculate()
print(f"\n{'='*60}")
print(f"Expected Value Analysis: {self.name}")
print(f"{'='*60}")
print(f"\nExpected Value: ${results['expected_value']:,.2f}")
print(f"Standard Deviation: ${results['std_deviation']:,.2f}")
print(f"Range: ${results['min_outcome']:,.2f} to ${results['max_outcome']:,.2f}")
print(f"\nScenarios:")
for s in self.scenarios:
print(f" {s['probability']*100:5.1f}% - ${s['outcome']:>10,.2f} - {s['description']}")
# Example usage
analysis = ExpectedValueAnalysis("New Market Entry")
analysis.add_scenario(2000000, 0.25, "High success")
analysis.add_scenario(500000, 0.45, "Moderate success")
analysis.add_scenario(-200000, 0.30, "Failure")
analysis.report()
Expected value is your rational foundation for decisions under uncertainty. Calculate it, understand its limitations, and use it alongside other metrics like standard deviation and worst-case analysis. The math is simple, but the insight it provides is invaluable.