"""Joint laws versus products of marginals; tested with Python 3.14.4. Uses the repository's matplotlib/numpy environment. Writes the PNG to CWD. """ from fractions import Fraction from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np def fraction(value): return str(Fraction(float(value)).limit_denominator(100)) def draw_table(ax, matrix, x_values, title): """Show every mass plus both marginals and the grand total.""" rows, columns = matrix.shape ax.axis("off") ax.set_title(title, fontsize=13, pad=10) data = [] for i in range(rows): data.append([fraction(v) for v in matrix[i]] + [fraction(matrix[i].sum())]) data.append([fraction(v) for v in matrix.sum(axis=0)] + ["1"]) table = ax.table( cellText=data, rowLabels=[f"X = {x}" for x in x_values] + ["P(Y = y)"], colLabels=[f"Y = {y}" for y in range(columns)] + ["P(X = x)"], cellLoc="center", rowLoc="center", bbox=[0.18, 0.03, 0.81, 0.86], ) table.auto_set_font_size(False) table.set_fontsize(12) for (row, col), cell in table.get_celld().items(): cell.set_edgecolor("#c3ccd6") if row == 0 or col == -1: cell.set_facecolor("#edf1f6") elif row == rows + 1 or col == columns: cell.set_facecolor("#edf6ed") else: value = matrix[row - 1, col] cell.set_facecolor(plt.cm.Blues(0.05 + 0.7 * value / 0.5)) cell.set_text_props(color="#182332") def main(): independent = np.full((2, 2), 0.25) identical = np.diag([0.5, 0.5]) squared = np.array([[0, 1 / 3], [1 / 3, 0], [0, 1 / 3]]) cases = [ (independent, [0, 1], "Independent fair bits: all four cells factor", 0), (identical, [0, 1], "Same fair bit, Y = X: matching marginals, different joint law", 0.25), (squared, [-1, 0, 1], "X uniform on {-1, 0, 1}, Y = X²: zero covariance, dependent", 0), ] plt.rcParams.update({"font.size": 12, "text.color": "#182332"}) fig = plt.figure(figsize=(9.4, 9.2), facecolor="white") grid = fig.add_gridspec(6, 2, height_ratios=[0.3, 1, 0.3, 1, 0.3, 1.2], hspace=0.30, wspace=0.18) for i, (joint, x_values, label, expected_covariance) in enumerate(cases): marginal_x = joint.sum(axis=1) marginal_y = joint.sum(axis=0) product = np.outer(marginal_x, marginal_y) assert np.isclose(joint.sum(), 1) assert np.isclose(product.sum(), 1) assert np.all(joint >= 0) assert np.allclose(product.sum(axis=1), marginal_x) assert np.allclose(product.sum(axis=0), marginal_y) x = np.array(x_values) y = np.arange(joint.shape[1]) covariance = np.sum(joint * x[:, None] * y[None, :]) - (x @ marginal_x) * (y @ marginal_y) assert np.isclose(covariance, expected_covariance) assert np.allclose(joint, product) == (i == 0) heading = fig.add_subplot(grid[2 * i, :]) heading.axis("off") heading.text(0.5, 0.5, label, ha="center", va="center", fontsize=12, weight="bold") draw_table(fig.add_subplot(grid[2 * i + 1, 0]), joint, x_values, "Actual joint probabilities") draw_table(fig.add_subplot(grid[2 * i + 1, 1]), product, x_values, "Product of the marginals") fig.suptitle("Independence requires equality in every joint cell", fontsize=17, weight="bold", y=0.98) fig.text(0.5, 0.02, "Blue: joint cells • Green: marginal totals • Each table sums to 1", ha="center", fontsize=11) fig.subplots_adjust(left=0.04, right=0.98, bottom=0.065, top=0.94) fig.savefig(Path.cwd() / "independent-and-dependent-probability-tables.png", dpi=100, facecolor="white", transparent=False) plt.close(fig) if __name__ == "__main__": main()