From abb70d6a98e660f59793d176103d17ebc6895798 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Oct 2026 09:33:51 +0100 Subject: [PATCH 1/5] DOC: Remove the spurious warnings from the Sphinx build The documentation build of main (121d869a06) ends with "build succeeded, 521 warnings" (533 in the last run on GitHub Actions, 37955470056; the footnotes of the rank tests have been fixed since). With this change the same build ends with "build succeeded, 3 warnings", with the packages of the CI run (Sphinx 9.1.0, sphinx-immaterial 0.13.9) on Linux with "-j 4", on Python 3.13 and 3.14. The 3 that remain are in docstrings and belong to the docstring work: the type list[str] in the docstrings of param_names in system/model.py. Only doc/source/conf.py and doc/source/nitpick-exceptions change. The 521: * NumPy scalars, 438 (numpy.float64 388, numpy.int64 48, numpy.int32 1, numpy.uint32 1). The array aliases expand in the signatures to ndarray[tuple[int, ...], dtype[float64]], and NumPy documents its scalar types as attributes, so the lookup of a class never succeeds. They are in nitpick-exceptions. The dtype stays in the signature, without a link. * TypeAliasForwardRef 44 (188 in a serial build), pd.DataFrame 2 and pd.Series 2. When a class docstring has a parameter without a type, napoleon in Sphinx 9 looks for the annotation of that name in the class and, as a side effect, merges napoleon_type_aliases into autodoc_type_aliases in place. From then on a nested annotation such as "DataFrame | None" is shown as "TypeAliasForwardRef('pandas.DataFrame') | None" with a warning that the class does not exist, and the dotted alias "pd.Series" turns pd into a pseudo module, so that "pd.DataFrame" cannot be evaluated and is shown as text. The signatures that are affected depend on the order in which the pages are built, which is why a parallel and a serial build disagree. napoleon_attr_annotations = False stops the lookup. The signatures are now shown like all others, and the types link to the pandas, NumPy and linearmodels documentation. The ndarray in the signature of LinearConstraint no longer links to IVData.ndarray. * InvalidTestStatistic, 6. The alias pointed to linearmodels.shared, but the class is documented in linearmodels.shared.hypotheses. * InapplicableTestStatistic, 4. It had no alias, and it is the type that pesaran_cd returns. * DataFrame, 10. The signatures of the NamedTuples PanelModelData (8) and HausmanTestResults (2) show the bare name, because autodoc cannot evaluate the annotations of the __new__ that NamedTuple generates. The python_type_aliases option of sphinx-immaterial expands it to pandas.DataFrame. * csc_array, 1. Added with csr_array and coo_array to the aliases. The alias np.ndarray pointed to numpy.array and now points to numpy.ndarray. * Private and undocumented classes, 8, which are in nitpick-exceptions: the _IVModelBase and _IVGMMBase in the annotations of the results classes (6), the protocol SupportsKeysAndGetItem of AttrDict.update (1), and the :class: role for functools.cached_property in changes/5.0.rst (1), which the Python documentation lists as a function. The exception for IVDataLike is removed because nothing refers to it any more. * AttrDict, 3. get, popitem and setdefault are inherited from collections.abc.MutableMapping. Their docstrings are not numpydoc, and napoleon turns them into cross-references to types that do not exist. A handler of autodoc-skip-member leaves out the members of collections.abc, so these three are no longer documented as methods of AttrDict. * list[str], 3, which stay. The docstring rules write "list of str". napoleon_type_aliases and numpydoc_xref_aliases were copies of each other with the same mistakes. They are now built from one dictionary. numpydoc is not in extensions, so its options still have no effect. Aliases are added for HausmanTestResults, AbsorbingLS and FamaMacBethResults, so that every estimator and results class has one, and for callable, scalar and sequence, which the docstring rules use and which resolve to the glossaries of Python and NumPy. ipykernel 7 also prints "Kernel is running over TCP without encryption" once for each notebook that nbsphinx runs, 12 in the CI log. They are not counted in the 521, but they look like warnings. nbsphinx_execute_arguments sets the kernel log level to ERROR. To reproduce the CI build, install the packages of the CI run and run, from doc/, "sphinx-build -M html source build -j 4". Pandoc is needed to convert the notebooks. --- doc/source/conf.py | 116 +++++++++++++++++----------------- doc/source/nitpick-exceptions | 18 +++++- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 72d9534069..201bd83121 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -305,9 +305,17 @@ napoleon_use_admonition_for_references = False napoleon_preprocess_types = True napoleon_use_param = True -napoleon_type_aliases = { - "array-like": ":term:`array-like `", - "array_like": ":term:`array_like`", +# Sphinx 9 napoleon merges napoleon_type_aliases into autodoc_type_aliases, in +# place, when a class docstring lists a parameter or an attribute without a type +# and napoleon looks for the annotation of that name in the class. From then on +# autodoc shows the annotation ``DataFrame | None`` of every signature that it +# builds as ``TypeAliasForwardRef('pandas.DataFrame') | None``, and warns that +# the class TypeAliasForwardRef cannot be found. Which signatures are affected +# depends on the order in which the pages are built. The docstrings give the +# types, so there is nothing to look up. +napoleon_attr_annotations = False +# Type names used in docstrings that are expanded to the object they refer to +type_xref_aliases = { "Figure": "matplotlib.figure.Figure", "Axes": "matplotlib.axes.Axes", "AxesSubplot": "matplotlib.axes.Axes", @@ -331,20 +339,24 @@ "RandomEffects": "linearmodels.panel.model.RandomEffects", "SUR": "linearmodels.system.model.SUR", "TradedFactorModel": "linearmodels.asset_pricing.model.TradedFactorModel", + "AbsorbingLS": "linearmodels.iv.absorbing.AbsorbingLS", "AbsorbingLSResults": "linearmodels.iv.absorbing.AbsorbingLSResults", "FirstStageResults": "linearmodels.iv.results.FirstStageResults", "IVGMMResults": "linearmodels.iv.results.IVGMMResults", "IVModelComparison": "linearmodels.iv.results.IVModelComparison", "IVResults": "linearmodels.iv.results.IVResults", - "InvalidTestStatistic": "linearmodels.shared.InvalidTestStatistic", + "InvalidTestStatistic": "linearmodels.shared.hypotheses.InvalidTestStatistic", + "InapplicableTestStatistic": "linearmodels.shared.hypotheses.InapplicableTestStatistic", "OLSResults": "linearmodels.iv.results.OLSResults", "WaldTestStatistic": "linearmodels.shared.hypotheses.WaldTestStatistic", "NormalTestStatistic": "linearmodels.shared.hypotheses.NormalTestStatistic", "LinearConstraint": "linearmodels.system.model.LinearConstraint", + "FamaMacBethResults": "linearmodels.panel.results.FamaMacBethResults", "PanelEffectsResults": "linearmodels.panel.results.PanelEffectsResults", "PanelModelComparison": "linearmodels.panel.results.PanelModelComparison", "PanelResults": "linearmodels.panel.results.PanelResults", "RandomEffectsResults": "linearmodels.panel.results.RandomEffectsResults", + "HausmanTestResults": "linearmodels.panel.results.HausmanTestResults", "GMMSystemResults": "linearmodels.system.results.GMMSystemResults", "Summary": "linearmodels.compat.statsmodels.Summary", "SystemEquationResult": "linearmodels.system.results.SystemEquationResult", @@ -355,71 +367,31 @@ "IVData": "linearmodels.iv.data.IVData", "AttrDict": "linearmodels.shared.utility.AttrDict", "csc_matrix": "scipy.sparse.csc_matrix", + "csc_array": "scipy.sparse.csc_array", + "csr_array": "scipy.sparse.csr_array", + "coo_array": "scipy.sparse.coo_array", "DataArray": "xarray.DataArray", "PanelModelData": "linearmodels.panel.utility.PanelModelData", "ndarray": "numpy.ndarray", - "np.ndarray": "numpy.array", + "np.ndarray": "numpy.ndarray", + "pd.DataFrame": "pandas.DataFrame", "pd.Series": "pandas.Series", } +napoleon_type_aliases = { + "array-like": ":term:`array-like `", + "array_like": ":term:`array_like`", + "callable": ":term:`callable`", + "scalar": ":term:`scalar`", + "sequence": ":term:`sequence`", + **type_xref_aliases, +} + # Create xrefs numpydoc_use_autodoc_signature = True numpydoc_xref_param_type = True numpydoc_class_members_toctree = False -numpydoc_xref_aliases = { - "Figure": "matplotlib.figure.Figure", - "Axes": "matplotlib.axes.Axes", - "AxesSubplot": "matplotlib.axes.Axes", - "DataFrame": "pandas.DataFrame", - "Series": "pandas.Series", - "BetweenOLS": "linearmodels.panel.model.BetweenOLS", - "FamaMacBeth": "linearmodels.panel.model.FamaMacBeth", - "FirstDifferenceOLS": "linearmodels.panel.model.FirstDifferenceOLS", - "IV2SLS": "linearmodels.iv.model.IV2SLS", - "IV3SLS": "linearmodels.system.model.IV3SLS", - "IVGMM": "linearmodels.iv.model.IVGMM", - "IVGMMCUE": "linearmodels.iv.model.IVGMMCUE", - "IVJIVE": "linearmodels.iv.model.IVJIVE", - "IVLIML": "linearmodels.iv.model.IVLIML", - "IVSystemGMM": "linearmodels.system.model.IVSystemGMM", - "LinearFactorModel": "linearmodels.asset_pricing.model.LinearFactorModel", - "LinearFactorModelGMM": "linearmodels.asset_pricing.model.LinearFactorModelGMM", - "OLS": "linearmodels.iv.model.OLS", - "PanelOLS": "linearmodels.panel.model.PanelOLS", - "PooledOLS": "linearmodels.panel.model.PooledOLS", - "RandomEffects": "linearmodels.panel.model.RandomEffects", - "SUR": "linearmodels.system.model.SUR", - "TradedFactorModel": "linearmodels.asset_pricing.model.TradedFactorModel", - "AbsorbingLSResults": "linearmodels.iv.absorbing.AbsorbingLSResults", - "FirstStageResults": "linearmodels.iv.results.FirstStageResults", - "IVGMMResults": "linearmodels.iv.results.IVGMMResults", - "IVModelComparison": "linearmodels.iv.results.IVModelComparison", - "IVResults": "linearmodels.iv.results.IVResults", - "InvalidTestStatistic": "linearmodels.shared.InvalidTestStatistic", - "OLSResults": "linearmodels.iv.results.OLSResults", - "WaldTestStatistic": "linearmodels.shared.hypotheses.WaldTestStatistic", - "NormalTestStatistic": "linearmodels.shared.hypotheses.NormalTestStatistic", - "LinearConstraint": "linearmodels.system.model.LinearConstraint", - "PanelEffectsResults": "linearmodels.panel.results.PanelEffectsResults", - "PanelModelComparison": "linearmodels.panel.results.PanelModelComparison", - "PanelResults": "linearmodels.panel.results.PanelResults", - "RandomEffectsResults": "linearmodels.panel.results.RandomEffectsResults", - "GMMSystemResults": "linearmodels.system.results.GMMSystemResults", - "Summary": "linearmodels.compat.statsmodels.Summary", - "SystemEquationResult": "linearmodels.system.results.SystemEquationResult", - "SystemResults": "linearmodels.system.results.SystemResults", - "GMMFactorModelResults": "linearmodels.asset_pricing.results.GMMFactorModelResults", - "LinearFactorModelResults": "linearmodels.asset_pricing.results.LinearFactorModelResults", - "PanelData": "linearmodels.panel.data.PanelData", - "IVData": "linearmodels.iv.data.IVData", - "AttrDict": "linearmodels.shared.utility.AttrDict", - "csc_matrix": "scipy.sparse.csc_matrix", - "DataArray": "xarray.DataArray", - "PanelModelData": "linearmodels.panel.utility.PanelModelData", - "ndarray": "numpy.ndarray", - "np.ndarray": "numpy.array", - "pd.Series": "pandas.Series", -} +numpydoc_xref_aliases = type_xref_aliases autosummary_generate = True autoclass_content = "class" @@ -429,3 +401,29 @@ "IntArray": "linearmodels.typing.data.IntArray", "Float64Array": "linearmodels.typing.data.Float64Array", } + +# sphinx_immaterial: autodoc cannot evaluate the annotations of the generated +# __new__ of a NamedTuple, so the signature of PanelModelData shows the bare +# name DataFrame, which cannot be resolved +python_type_aliases = {"DataFrame": "pandas.DataFrame"} + +# nbsphinx starts one kernel for each notebook. ipykernel 7 logs a warning each +# time a kernel starts over TCP without encryption, which is the default, and +# Sphinx prints it as if it were a warning of the documentation build. +nbsphinx_execute_arguments = ["--IPKernelApp.log_level=ERROR"] + + +def skip_collections_abc_members(app, what, name, obj, skip, options): + """ + Do not document the methods that AttrDict inherits from collections.abc + + The docstrings of get, popitem and setdefault are not numpydoc. Napoleon + turns them into cross-references to types which do not exist. + """ + if getattr(obj, "__module__", None) == "collections.abc": + return True + return None + + +def setup(app): + app.connect("autodoc-skip-member", skip_collections_abc_members) diff --git a/doc/source/nitpick-exceptions b/doc/source/nitpick-exceptions index c6f648dd5e..92e4421c5e 100644 --- a/doc/source/nitpick-exceptions +++ b/doc/source/nitpick-exceptions @@ -1,6 +1,22 @@ # py:class ArrayLike -py:class IVDataLike # py:class IntArray # py:class Float64Array +# NumPy documents its sized scalar types as attributes, not as classes, so the +# py:class cross-references to the dtypes in the array type aliases never resolve. +# Add a line here if another dtype shows up in the type of a documented object. +py:class numpy.float64 +py:class numpy.int64 +py:class numpy.int32 +py:class numpy.uint32 +# Private classes used in the annotations of public classes and methods +py:class linearmodels.iv.model._IVModelBase +py:class linearmodels.iv.model._IVGMMBase + +# Protocol of the arguments of AttrDict.update +py:class linearmodels.shared.utility.SupportsKeysAndGetItem + +# changes/5.0.rst: the Python documentation lists functools.cached_property as a +# function (decorator), so the :class: role used there does not find it. +py:class functools.cached_property From fb1a1d7a267036ae076cc12fe4cb2c4535af383d Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Oct 2026 09:33:51 +0100 Subject: [PATCH 2/5] DOC: Update the copyright years of the documentation The footer of the documentation says "Copyright 2021, Kevin Sheppard", but the package has been released since 2017 (LICENSE.md also says 2017). It now says 2017 to the year in which the documentation is built. Sphinx replaces the last year when SOURCE_DATE_EPOCH is set, so builds with that variable stay reproducible. --- doc/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 201bd83121..67386e563f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -7,6 +7,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import datetime import glob import hashlib import os @@ -18,7 +19,9 @@ # -- Project information ----------------------------------------------------- project = "linearmodels" -copyright = "2021, Kevin Sheppard" +# The first year is the one in LICENSE.md. Sphinx corrects the last year when +# SOURCE_DATE_EPOCH is set, so reproducible builds remain reproducible. +copyright = f"2017-{datetime.date.today().year}, Kevin Sheppard" author = "Kevin Sheppard" # More warnings From 4fe6ca1f39495bee053161ddc63b04178115c07a Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Oct 2026 09:33:51 +0100 Subject: [PATCH 3/5] DOC: Make Past Releases a section of the change log changes.rst gives the title "Past Releases" an overline and an underline. No other title has that style, so docutils takes it for a new level below the deepest one used so far, and the heading becomes a subsection of the last section above it: on the published page it is an h3 inside "Version 7.0". Subsections in the included notes would push it down even further, which is why the 7.1 notes use rubrics instead of sections. With an underline of dashes, like the versions, "Past Releases" is a section of the same level as "Version 7.1" and "Version 7.0", which end the included notes, and the notes are free to use sections of their own. The page builds with no new warning and the home page does not change. An underline of equals signs, the style of "Change Log", would also work but would add "Past Releases" to the home page as a second top-level entry of the page. --- doc/source/changes.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index ada38ec10f..2baa06773e 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -3,9 +3,8 @@ Change Log .. include:: changes/7.0.rst -============= Past Releases -============= +------------- .. toctree:: :maxdepth: 1 From 8d545a2d41bfd9e80a43a7676dcda80b6a7032de Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Oct 2026 09:33:52 +0100 Subject: [PATCH 4/5] DOC: Add the new names and terms to the spelling word lists "sphinx-build -b spelling" reports 189 misspelled words, 52 different ones, in the documentation of main. Nearly all of them come from the text of 7.1. After this change 3 reports remain, for the 2 words that are mistakes in the text. names_wordlist.txt: * the authors and the journal cited by the new docstrings: Pesaran, Juodis, Angrist, Chao, Imbens, Krueger, Woutersen, Hayashi and Econometrica * the same names as they are written in function names (cragg_donald, kleibergen_paap, pesaran_cd), because the lower case form of a word is not accepted when only the capitalised form is listed * the GitHub user names in the contributors of the 7.1 release notes spelling_wordlist.txt: * the classes that are new or that were never in the list and that appear in the titles of the reference pages: GMMFactorModelResults, IVJIVE, OLS, PanelModelData, AbsorbingRegressor, HausmanTestResults, InapplicableTestStatistic, LinearConstraint and NormalTestStatistic * statsmodels, compat, csv, html, txt, gleft, gright, xname, yname and num, which are in the names of the Summary methods and their arguments * nan, rc (NumPy 2.0.0rc1), ranktest (a Stata module) and the words autocorrelated, misspecification, partialled (used 9 times in the package), regressors, reindex, docstring and numpydoc Two words stay on the list of the spelling builder on purpose, because they are mistakes to fix in the text and not words to accept: "rsquare" in the docstrings of FamaMacBethResults.avg_rsquared and avg_adj_rsquared, and "fuure" in changes/6.0.rst. --- doc/source/names_wordlist.txt | 21 +++++++++++++++++++++ doc/source/spelling_wordlist.txt | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/doc/source/names_wordlist.txt b/doc/source/names_wordlist.txt index 1b8be38392..f56f7bd165 100644 --- a/doc/source/names_wordlist.txt +++ b/doc/source/names_wordlist.txt @@ -51,3 +51,24 @@ Donald Yogo Olea Pflueger +Pesaran +pesaran +Juodis +Angrist +Chao +Imbens +Krueger +Woutersen +Hayashi +Econometrica +cragg +donald +kleibergen +paap +shaneraphel +aeshef +hass +maxime +nyxst +raashish +shuxue diff --git a/doc/source/spelling_wordlist.txt b/doc/source/spelling_wordlist.txt index f24df71fc9..10934843a3 100644 --- a/doc/source/spelling_wordlist.txt +++ b/doc/source/spelling_wordlist.txt @@ -205,3 +205,32 @@ pyhdfe absorber underidentification underidentified +GMMFactorModelResults +IVJIVE +OLS +PanelModelData +AbsorbingRegressor +HausmanTestResults +InapplicableTestStatistic +LinearConstraint +NormalTestStatistic +compat +statsmodels +csv +html +txt +gleft +gright +xname +yname +num +nan +rc +regressors +reindex +autocorrelated +misspecification +partialled +ranktest +docstring +numpydoc From 0a9c90b5b3b80fa39149e19410d0a6809c352bba Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 12 Oct 2026 09:34:31 +0100 Subject: [PATCH 5/5] MAINT: Ignore the API pages that autosummary writes for three pages Every docs build writes the autosummary pages of compatibility.rst, utility.rst and the AbsorbingLS section of iv/reference.rst into doc/source/compatibility/, doc/source/utility/ and doc/source/iv/absorbing/. .gitignore covered the folders of the other reference pages but not these three, so a local build left them as untracked files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b2d9fe97a9..23f0651a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,9 @@ doc/source/asset-pricing/asset-pricing/ doc/source/iv/iv/ doc/source/panel/panel/ doc/source/system/system/ +doc/source/compatibility/ +doc/source/iv/absorbing/ +doc/source/utility/ # PyInstaller # Usually these files are written by a python script from a template