---
title: "Bayesian games — strategic interaction under incomplete information"
description: "Formalise games of incomplete information in R, compute Bayes-Nash equilibria for type-dependent strategies, and visualize how private information shapes equilibrium behaviour."
author: "Raban Heller"
date: 2026-05-08
date-modified: 2026-05-08
categories:
- bayesian-methods
- bayesian-games
- incomplete-information
- bayes-nash
keywords: ["Bayesian game", "incomplete information", "Bayes-Nash equilibrium", "types", "beliefs", "Harsanyi"]
labels: ["bayesian-gt", "equilibrium-concepts"]
tier: 1
bibliography: ../../../references.bib
vgwort: "TODO_VGWORT_bayesian-methods_bayesian-games-incomplete-information"
image: thumbnail.png
image-alt: "Type-contingent equilibrium strategies in a Bayesian game"
citation:
type: webpage
url: https://r-heller.github.io/equilibria/tutorials/bayesian-methods/bayesian-games-incomplete-information/
license: "CC BY-SA 4.0"
draft: false
has_static_fig: true
has_interactive_fig: true
has_shiny_app: false
---
```{r}
#| label: setup
#| include: false
library(ggplot2)
library(dplyr)
library(tidyr)
library(plotly)
okabe_ito <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442",
"#0072B2", "#D55E00", "#CC79A7", "#999999")
theme_publication <- function(base_size = 12) {
theme_minimal(base_size = base_size) +
theme(plot.title = element_text(size = base_size * 1.2, face = "bold"),
plot.subtitle = element_text(size = base_size * 0.9, color = "grey40"),
axis.line = element_line(color = "grey30", linewidth = 0.3),
panel.grid.minor = element_blank(), legend.position = "bottom",
plot.margin = margin(10, 10, 10, 10))
}
```
## Introduction & motivation
In many strategic situations, players hold **private information** — a firm does not know a competitor's costs, a buyer does not know a seller's reservation price, a country does not know an adversary's resolve. John @harsanyi_1973 showed how to handle this by introducing **types**: each player's private information is summarised as a "type" drawn from a commonly known probability distribution. A **Bayesian game** combines the strategic structure of a normal-form game with this type uncertainty, and the solution concept is the **Bayes-Nash equilibrium** (BNE): a strategy profile where each type of each player best-responds given their beliefs about others' types. Harsanyi's framework unified the treatment of incomplete information in game theory and earned him the 1994 Nobel Prize alongside Nash and Selten. This tutorial formalises Bayesian games, implements BNE computation for discrete-type and continuous-type games, and demonstrates how private information changes equilibrium behaviour relative to complete-information benchmarks — with applications to pricing under cost uncertainty and entry deterrence with unknown strength.
## Mathematical formulation
A **Bayesian game** $G = (N, \{A_i\}, \{\Theta_i\}, \{u_i\}, \{p_i\})$ consists of:
- Players $N = \{1, \ldots, n\}$
- Action sets $A_i$ for each player
- Type spaces $\Theta_i$ with common prior $p(\theta)$
- Utility functions $u_i(a, \theta)$ depending on the action profile and type profile
A (pure) strategy is a function $s_i: \Theta_i \to A_i$. A **Bayes-Nash equilibrium** is a strategy profile $s^*$ such that for all $i$, all $\theta_i \in \Theta_i$:
$$
s_i^*(\theta_i) \in \arg\max_{a_i \in A_i} E_{\theta_{-i}}[u_i(a_i, s_{-i}^*(\theta_{-i}), \theta_i, \theta_{-i}) \mid \theta_i]
$$
Each type maximises expected utility given their beliefs about others' types and strategies.
## R implementation
```{r}
#| label: bayesian-game-example
# --- Example: Cournot duopoly with cost uncertainty ---
# Firm 1 knows its cost c1; Firm 2's cost c2 is either Low or High
# Inverse demand: P(Q) = a - Q, Q = q1 + q2
a <- 10 # demand intercept
# Firm 1: c1 = 2 (known)
c1 <- 2
# Firm 2: c2_L = 1 (prob 0.5), c2_H = 4 (prob 0.5)
c2_L <- 1; c2_H <- 4; prob_L <- 0.5
# BNE: Each type of Firm 2 best-responds; Firm 1 best-responds to expected behaviour
# Firm 1 BR: q1 = (a - c1 - E[q2]) / 2
# Firm 2 type L BR: q2_L = (a - c2_L - q1) / 2
# Firm 2 type H BR: q2_H = (a - c2_H - q1) / 2
# E[q2] = prob_L * q2_L + (1-prob_L) * q2_H
# Solve system:
# q1 = (a - c1 - prob_L * q2_L - (1-prob_L) * q2_H) / 2
# q2_L = (a - c2_L - q1) / 2
# q2_H = (a - c2_H - q1) / 2
# Substituting:
# q2_L = (a - c2_L)/2 - q1/2
# q2_H = (a - c2_H)/2 - q1/2
# E[q2] = prob_L*(a-c2_L)/2 + (1-prob_L)*(a-c2_H)/2 - q1/2
# q1 = (a - c1)/2 - E[q2]/2
# q1 = (a-c1)/2 - [prob_L*(a-c2_L)/2 + (1-prob_L)*(a-c2_H)/2 - q1/2]/2
E_c2 <- prob_L * c2_L + (1 - prob_L) * c2_H
# Simplified: standard Cournot with Firm 1 facing E[c2] adjustment
# q1 = (a - c1 - (a - E_c2)/2 + q1/4) ... solving the 3-equation system
# Direct solve via linear system: Ax = b
# q1 + 0.5*prob_L*q2_L + 0.5*(1-prob_L)*q2_H = (a-c1)/2
# 0.5*q1 + q2_L = (a-c2_L)/2
# 0.5*q1 + q2_H = (a-c2_H)/2
A_mat <- matrix(c(1, prob_L/2, (1-prob_L)/2,
0.5, 1, 0,
0.5, 0, 1), nrow = 3, byrow = TRUE)
b_vec <- c((a - c1)/2, (a - c2_L)/2, (a - c2_H)/2)
sol <- solve(A_mat, b_vec)
q1_star <- sol[1]; q2_L_star <- sol[2]; q2_H_star <- sol[3]
cat("=== Cournot Duopoly with Cost Uncertainty ===\n")
cat(sprintf("Firm 1 (c1=%.0f): q1* = %.3f\n", c1, q1_star))
cat(sprintf("Firm 2 Low cost (c2=%.0f): q2_L* = %.3f\n", c2_L, q2_L_star))
cat(sprintf("Firm 2 High cost (c2=%.0f): q2_H* = %.3f\n", c2_H, q2_H_star))
cat(sprintf("\nComplete-info benchmark (c2=E[c2]=%.1f):\n", E_c2))
q1_ci <- (a - 2*c1 + E_c2) / 3
q2_ci <- (a - 2*E_c2 + c1) / 3
cat(sprintf(" q1 = %.3f, q2 = %.3f\n", q1_ci, q2_ci))
cat("(Incomplete information changes quantities: low-cost type produces more, high-cost less)\n")
```
## Static publication-ready figure
```{r}
#| label: fig-bayesian-cournot
#| fig-cap: "Figure 1. Cournot equilibrium outputs under complete vs incomplete information. Under incomplete information, Firm 2's low-cost type produces more and high-cost type produces less than the complete-information benchmark. Firm 1, facing type uncertainty, adjusts output to the expected competitor. The information gap creates efficiency losses compared to the full-information case. Okabe-Ito palette."
#| dev: [png, pdf]
#| fig-width: 7
#| fig-height: 5
#| dpi: 300
output_df <- tibble(
scenario = c("Complete info", "BNE: Firm 1", "BNE: Firm 2 (Low)", "BNE: Firm 2 (High)"),
q = c(q1_ci, q1_star, q2_L_star, q2_H_star),
player = c("Benchmark", "Firm 1", "Firm 2", "Firm 2"),
type = c("Complete info", "Uncertain", "Low cost", "High cost")
) |> mutate(scenario = factor(scenario, levels = scenario))
ggplot(output_df, aes(x = scenario, y = q, fill = type)) +
geom_col(width = 0.6) +
geom_text(aes(label = round(q, 2)), vjust = -0.3, size = 3.5) +
scale_fill_manual(values = c("Complete info" = okabe_ito[8], "Uncertain" = okabe_ito[5],
"Low cost" = okabe_ito[3], "High cost" = okabe_ito[6]),
name = "Type") +
labs(title = "Cournot outputs: complete vs incomplete information",
subtitle = "Bayesian game with cost uncertainty (P(low)=0.5)",
x = NULL, y = "Quantity produced") +
theme_publication()
```
## Interactive figure
```{r}
#| label: fig-bne-sensitivity
prob_seq <- seq(0.01, 0.99, 0.02)
bne_data <- lapply(prob_seq, function(p) {
A_m <- matrix(c(1, p/2, (1-p)/2, 0.5, 1, 0, 0.5, 0, 1), nrow = 3, byrow = TRUE)
b_v <- c((a-c1)/2, (a-c2_L)/2, (a-c2_H)/2)
s <- solve(A_m, b_v)
tibble(prob_low = p, q1 = s[1], q2_L = s[2], q2_H = s[3])
}) |> bind_rows() |>
pivot_longer(-prob_low, names_to = "player", values_to = "quantity") |>
mutate(text = paste0("P(low cost) = ", round(prob_low, 2), "\n", player, " = ", round(quantity, 3)))
p_sens <- ggplot(bne_data, aes(x = prob_low, y = quantity, color = player, text = text)) +
geom_line(linewidth = 1) +
scale_color_manual(values = c(q1 = okabe_ito[5], q2_L = okabe_ito[3], q2_H = okabe_ito[6]),
labels = c("Firm 1", "Firm 2 (Low)", "Firm 2 (High)"), name = "Player/Type") +
labs(title = "BNE quantities vs prior belief about cost type",
subtitle = "As P(low cost) increases, Firm 1 expects tougher competition → produces less",
x = "P(Firm 2 has low cost)", y = "Equilibrium quantity") +
theme_publication()
ggplotly(p_sens, tooltip = "text") |>
config(displaylogo = FALSE, modeBarButtonsToRemove = c("select2d", "lasso2d"))
```
## Interpretation
Bayesian games reveal how private information fundamentally reshapes strategic interaction. In the Cournot example, Firm 2's private cost information creates a separation of types: the low-cost type produces aggressively (leveraging its cost advantage) while the high-cost type retreats. Firm 1, unable to observe Firm 2's type, must respond to an expected competitor — producing a compromise quantity. The sensitivity analysis shows that beliefs matter: as Firm 1 becomes more convinced that Firm 2 has low costs, it reduces its own output (expecting tougher competition), while the low-cost type expands and the high-cost type's output barely changes. @harsanyi_1973's framework transformed this from an intractable problem ("how do you play a game when you don't know your opponent's payoffs?") into a standard optimisation problem with beliefs. The key insight is that incomplete information does not make games unsolvable — it makes them richer, with strategies contingent on private types and equilibria reflecting the interplay of information and incentives.
## Extensions & related tutorials
- [Mixed-strategy Nash equilibrium](../../foundations/nash-equilibrium-mixed/) — the complete-information foundation.
- [First-price sealed-bid auction](../../auction-theory-deep-dive/first-price-sealed-bid/) — BNE in auction settings.
- [Vickrey auction and DSIC](../../mechanism-design/vickrey-second-price-auction/) — mechanism design with private values.
- [Cuban Missile Crisis as signaling game](../../real-world-data-applications/cuban-missile-crisis-signaling-game/) — applied incomplete information.
- [Signaling games and PBE](../../foundations/signaling-games-pbe/) — extending to sequential incomplete-info games.
## References
::: {#refs}
:::