Skip to content

Commit 63da7b1

Browse files
committed
fairlex
1 parent 924bd82 commit 63da7b1

7 files changed

Lines changed: 1521 additions & 7 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ jobs:
3030
- name: Set up Python
3131
run: uv python install
3232

33+
- name: Install pandoc
34+
run: sudo apt-get update && sudo apt-get install -y pandoc
35+
3336
- name: Install dependencies
3437
run: uv sync --group docs
3538

docs/conf.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Configuration file for the Sphinx documentation builder."""
22

3-
import os
43
import sys
5-
from pathlib import Path
64
from importlib.metadata import metadata
5+
from pathlib import Path
76

87
# Add the project root to the Python path
98
sys.path.insert(0, str(Path(__file__).parent.parent))
@@ -14,7 +13,7 @@
1413
pkg_metadata = metadata("fairlex")
1514

1615
project = pkg_metadata["Name"]
17-
# Extract author from Author-email field
16+
# Extract author from Author-email field
1817
author_email = pkg_metadata["Author-email"]
1918
# Parse "Name <email>" format
2019
author = author_email.split("<")[0].strip()
@@ -33,6 +32,7 @@
3332
"sphinx.ext.viewcode",
3433
"sphinx.ext.intersphinx",
3534
"sphinx_autodoc_typehints",
35+
"nbsphinx",
3636
]
3737

3838
# Add any paths that contain templates here, relative to this directory.
@@ -88,4 +88,24 @@
8888
typehints_fully_qualified = False
8989
always_document_param_types = True
9090
typehints_document_rtype = True
91-
typehints_use_rtype = True
91+
typehints_use_rtype = True
92+
93+
# -- nbsphinx configuration ------------------------------------------------
94+
95+
# Execute notebooks during build
96+
nbsphinx_execute = "always"
97+
98+
# Allow errors during execution for debugging
99+
nbsphinx_allow_errors = True
100+
101+
# Use Python 3 kernel
102+
nbsphinx_kernel_name = "python3"
103+
104+
# 10-minute timeout for notebook execution
105+
nbsphinx_timeout = 600
106+
107+
# Configure figure formats and DPI for better output
108+
nbsphinx_execute_arguments = [
109+
"--InlineBackend.figure_formats={'svg', 'pdf'}",
110+
"--InlineBackend.rc={'figure.dpi': 96}",
111+
]

docs/examples.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Examples
2+
========
3+
4+
This section contains practical examples demonstrating how to use fairlex for survey weight calibration.
5+
6+
Basic Example
7+
-------------
8+
9+
Learn the fundamentals of leximin calibration with a simple synthetic dataset.
10+
11+
.. toctree::
12+
:maxdepth: 2
13+
14+
examples/basic_example

docs/examples/basic_example.ipynb

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Basic Example: Fairlex Calibration\n",
8+
"\n",
9+
"This example illustrates how to construct a membership matrix and target\n",
10+
"totals, call the weight-fair leximin calibration, and inspect the resulting\n",
11+
"weights and diagnostics."
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": null,
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"import numpy as np\n",
21+
"from fairlex import evaluate_solution, leximin_weight_fair"
22+
]
23+
},
24+
{
25+
"cell_type": "markdown",
26+
"metadata": {},
27+
"source": [
28+
"## Setting up the problem\n",
29+
"\n",
30+
"Suppose we survey five people and want to calibrate on sex and age.\n",
31+
"Each margin is represented by two rows: the indicator for the\n",
32+
"first category and the second category. We also include a total row."
33+
]
34+
},
35+
{
36+
"cell_type": "code",
37+
"execution_count": null,
38+
"metadata": {},
39+
"outputs": [],
40+
"source": [
41+
"# Define the membership matrix A\n",
42+
"A = np.array(\n",
43+
" [\n",
44+
" # sex: female\n",
45+
" [1, 0, 1, 0, 1],\n",
46+
" # sex: male\n",
47+
" [0, 1, 0, 1, 0],\n",
48+
" # age: young (<=40)\n",
49+
" [1, 1, 0, 0, 1],\n",
50+
" # age: old (>40)\n",
51+
" [0, 0, 1, 1, 0],\n",
52+
" # total\n",
53+
" [1, 1, 1, 1, 1],\n",
54+
" ],\n",
55+
" dtype=float,\n",
56+
")\n",
57+
"\n",
58+
"print(\"Membership matrix A:\")\n",
59+
"print(A)"
60+
]
61+
},
62+
{
63+
"cell_type": "code",
64+
"execution_count": null,
65+
"metadata": {},
66+
"outputs": [],
67+
"source": [
68+
"# Base weights (e.g. equal weights in a simple random sample)\n",
69+
"w0 = np.ones(5)\n",
70+
"print(\"Base weights:\", w0)\n",
71+
"\n",
72+
"# Target totals for the population (feasible with max weight 2.0 per person)\n",
73+
"# Note: with 5 people and max weight 2.0, total achievable is 10\n",
74+
"target = np.array([6, 4, 6, 4, 10], dtype=float)\n",
75+
"print(\"Target totals:\", target)"
76+
]
77+
},
78+
{
79+
"cell_type": "markdown",
80+
"metadata": {},
81+
"source": [
82+
"## Performing calibration\n",
83+
"\n",
84+
"We use the weight-fair leximin calibration with modest weight bounds."
85+
]
86+
},
87+
{
88+
"cell_type": "code",
89+
"execution_count": null,
90+
"metadata": {},
91+
"outputs": [],
92+
"source": [
93+
"# Perform calibration with modest bounds\n",
94+
"result = leximin_weight_fair(A, target, w0, min_ratio=0.5, max_ratio=2.0)\n",
95+
"print(\"Calibrated weights:\", result.w)\n",
96+
"print(\"Optimization status:\", result.status)\n",
97+
"print(\"Solver message:\", result.message)"
98+
]
99+
},
100+
{
101+
"cell_type": "markdown",
102+
"metadata": {},
103+
"source": [
104+
"## Evaluating the solution\n",
105+
"\n",
106+
"Let's examine the quality of our calibrated weights using various diagnostics."
107+
]
108+
},
109+
{
110+
"cell_type": "code",
111+
"execution_count": null,
112+
"metadata": {},
113+
"outputs": [],
114+
"source": [
115+
"# Evaluate solution quality\n",
116+
"metrics = evaluate_solution(A, target, result.w, base_weights=w0)\n",
117+
"\n",
118+
"print(\"Solution Diagnostics:\")\n",
119+
"print(\"====================\")\n",
120+
"for key, value in metrics.items():\n",
121+
" print(f\"{key}: {value:.4f}\")"
122+
]
123+
},
124+
{
125+
"cell_type": "markdown",
126+
"metadata": {},
127+
"source": [
128+
"## Understanding the results\n",
129+
"\n",
130+
"The diagnostics show us:\n",
131+
"- **Residual metrics**: How well we achieved our target totals\n",
132+
"- **Weight distribution**: Statistical properties of the final weights\n",
133+
"- **Relative deviations**: How much the weights changed from their base values\n",
134+
"- **ESS (Effective Sample Size)**: Measure of variance inflation\n",
135+
"- **Design Effect**: Ratio of actual to nominal sample size"
136+
]
137+
}
138+
],
139+
"metadata": {
140+
"kernelspec": {
141+
"display_name": "Python 3",
142+
"language": "python",
143+
"name": "python3"
144+
},
145+
"language_info": {
146+
"codemirror_mode": {
147+
"name": "ipython",
148+
"version": 3
149+
},
150+
"file_extension": ".py",
151+
"mimetype": "text/x-python",
152+
"name": "python",
153+
"nbconvert_exporter": "python",
154+
"pygments_lexer": "ipython3",
155+
"version": "3.12.0"
156+
}
157+
},
158+
"nbformat": 4,
159+
"nbformat_minor": 4
160+
}

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Contents
8787
.. toctree::
8888
:maxdepth: 2
8989

90+
examples
9091
api
9192

9293
Indices and tables

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ docs = [
4747
"sphinx>=7.0",
4848
"furo>=2024.1.29",
4949
"sphinx-autodoc-typehints>=1.25",
50+
"nbsphinx>=0.9.0",
51+
"ipython>=8.0.0",
52+
"jupyter>=1.0.0",
53+
"pandoc>=2.0",
5054
]
5155

5256
[project.urls]
@@ -67,7 +71,8 @@ exclude = [
6771
"build",
6872
"dist",
6973
".venv",
70-
"docs"
74+
"docs",
75+
"examples"
7176
]
7277

7378
[tool.ruff.lint]
@@ -97,7 +102,7 @@ ignore = [
97102
[tool.ruff.lint.per-file-ignores]
98103
"__init__.py" = ["F401"]
99104
"tests/*" = ["T201", "PLR2004"]
100-
"examples/*" = ["T201"]
105+
"docs/examples/*" = ["T201"]
101106

102107
[tool.ruff.lint.isort]
103108
known-first-party = ["fairlex"]

0 commit comments

Comments
 (0)