Repository: vanderschaarlab/temporai (language: python)

Issue:
Fix test failure due to `cmaes` import failure with optuna 3.4+



Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 731a42ec..b5940265 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,7 @@
 
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.4.0
+  rev: v4.5.0
   hooks:
   - id: trailing-whitespace
     args: [
@@ -48,7 +48,7 @@ repos:
   - id: isort
 
 - repo: https://github.com/psf/black
-  rev: 23.7.0
+  rev: 23.11.0
   hooks:
   - id: black-jupyter
     language_version: python3
@@ -64,13 +64,14 @@ repos:
     ]
     exclude: "docs/user_guide"
 
+# Standard `flake8` run for all Python files:
 - repo: https://github.com/PyCQA/flake8
   rev: 6.1.0
   hooks:
   - id: flake8
 
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v1.5.1
+  rev: v1.6.1
   hooks:
   - id: mypy
     files: "^src/"
@@ -104,3 +105,31 @@ repos:
     additional_dependencies: [
       "bandit[toml]",
     ]
+
+- repo: https://github.com/pycqa/pydocstyle
+  rev: 6.3.0
+  hooks:
+  - id: pydocstyle
+    additional_dependencies: [
+      "tomli",  # To read configuration from pyproject.toml.
+    ]
+    exclude: |
+      (?x)^(
+          docs/.*|
+          tests/.*
+      )$
+
+# `pydoclint` run via `flake8`, with some additional locations ignored.
+- repo: https://github.com/PyCQA/flake8
+  rev: 6.1.0
+  hooks:
+  - id: flake8
+    name: "pydoclint_via_flake8"
+    additional_dependencies: [
+      "pydoclint",
+    ]
+    exclude: |
+      (?x)^(
+          docs/.*|
+          tests/.*
+      )$
diff --git a/docs/conf.py b/docs/conf.py
index 0394f9fb..ea132374 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -21,6 +21,9 @@
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 sys.path.insert(0, os.path.join(__location__, "../src"))
 
+# Any custom sphinx extensions for TemporAI live in docs/custom-sphinx-ext/:
+sys.path.insert(0, os.path.join(__location__, "custom-sphinx-ext"))
+
 # -- Run sphinx-apidoc -------------------------------------------------------
 # This hack is necessary since RTD does not issue `sphinx-apidoc` before running
 # `sphinx-build -b html . _build/html`. See Issue:
@@ -77,6 +80,8 @@
     # "sphinx_immaterial.apidoc.python.apigen"
     # ^ Enable this if wishing to use https://jbms.github.io/sphinx-immaterial/apidoc/python/apigen.html
     "nbsphinx",
+    # --- Custom extensions from here ---
+    "sphinx-zeta-suppress",  # More specific warnings suppression.
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -405,6 +410,42 @@
 # If false, no module index is generated.
 # latex_domain_indices = True
 
+
+# `sphinx-zeta-suppress` (more specific warnings suppression) configuration.
+#
+# See:
+# - https://sphinx-zeta-suppress.readthedocs.io/
+#
+# See also:
+# - https://github.com/picnixz/sphinx-zeta-suppress
+# - https://github.com/sphinx-doc/sphinx/issues/11325
+# Note that since there is no PyPI package for sphinx-zeta-suppress, we add its python module as a custom extension,
+# see: docs/custom-sphinx-ext/sphinx-zeta-suppress.py.
+
+zeta_suppress_protect = [
+    "sphinx_immaterial"
+    # `sphinx-zeta-suppress` is not compatible with `sphinx_immaterial`. It will throw an error when registering the
+    # filters (`_setup_filters` function). However, the problem can be overcome by adding `sphinx_immaterial` to the
+    # `zeta_suppress_protect` list.
+    # Note we can still suppress warnings specific to `sphinx_immaterial` by adding it to
+    # the `zeta_suppress_records` list below.
+]
+
+zeta_suppress_records = [
+    # The following warnings caused ultimately by sphinx_immaterial are caused by having *args/**kwargs in the
+    # docstrings. We want to have those in docstrings, so we suppress these warnings.
+    ["sphinx_immaterial", r".*Parameter name '\*args'.*"],
+    ["sphinx_immaterial", r".*Parameter name '\*\*kwargs'.*"],
+    # Similar but for other variable names used for the variadics:
+    ["sphinx_immaterial", r".*Parameter name '\*dims'.*"],
+    ["sphinx_immaterial", r".*Parameter name '\*\*params'.*"],
+    # The annotations for `PipelineMeta` seem to have their own set of issues, ignore here:
+    ["sphinx_immaterial", r".*Parameter name.*PipelineMeta.*"],
+]
+
+# `sphinx-zeta-suppress` (more specific warnings suppression) configuration [end].
+
+
 # -- External mapping --------------------------------------------------------
 python_version = ".".join(map(str, sys.version_info[0:2]))
 intersphinx_mapping = {
diff --git a/docs/custom-sphinx-ext/sphinx-zeta-suppress.py b/docs/custom-sphinx-ext/sphinx-zeta-suppress.py
new file mode 100644
index 00000000..443ebda3
--- /dev/null
+++ b/docs/custom-sphinx-ext/sphinx-zeta-suppress.py
@@ -0,0 +1,395 @@
+# type: ignore
+# flake8: noqa
+# pylint: skip-file
+
+"""A Sphinx extension for more fine-grained control of Sphinx WARNING/ERROR log messages.
+
+This is adapted directly from https://github.com/picnixz/sphinx-zeta-suppress with minimal modifications beyond
+code formatting.
+
+See resources:
+- https://sphinx-zeta-suppress.readthedocs.io/
+- https://github.com/picnixz/sphinx-zeta-suppress
+- https://github.com/sphinx-doc/sphinx/issues/11325
+"""
+
+from __future__ import annotations
+
+__all__ = ()
+
+import abc
+import contextlib
+import importlib
+import inspect
+import logging
+import pkgutil
+import re
+import warnings
+from itertools import filterfalse, tee
+from typing import TYPE_CHECKING, TypeVar
+
+from sphinx.errors import ExtensionError
+from sphinx.util.logging import NAMESPACE, SphinxLoggerAdapter, getLogger
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Generator, Iterable, Iterator
+    from types import ModuleType
+    from typing import Any, Literal
+
+    from sphinx.application import Sphinx
+    from sphinx.config import Config
+    from sphinx.extension import Extension
+    from typing_extensions import TypeGuard
+
+    T = TypeVar("T")
+
+#: Logging level type.
+Level = TypeVar("Level", int, str)
+
+logger = getLogger(__name__)
+
+
+def _notnone(value):
+    # type: (Any) -> bool
+    return value is not None
+
+
+def _is_sphinx_logger_adapter(value):
+    # type: (Any) -> TypeGuard[SphinxLoggerAdapter]
+    return isinstance(value, SphinxLoggerAdapter)
+
+
+def _is_pattern_like(obj):
+    # type: (Any) -> TypeGuard[str | re.Pattern]
+    return isinstance(obj, (str, re.Pattern))
+
+
+def _partition(predicate, iterable):
+    # type: (Callable[[T], bool], Iterable[T]) -> (Iterator[T], Iterator[T])
+    """Partition an iterable into two iterators according to *predicate*.
+
+    The result is `(no, yes)` of iterators such that *no* and *yes* iterate
+    over the values in *iterable* for which *predicate* is falsey and truthy
+    respectively.
+
+    Typical usage::
+
+        odd, even = partition(lambda x: x % 2 == 0, range(10))
+
+        assert list(odd) == [1, 3, 5, 7, 8]
+        assert list(even) == [0, 2, 4, 6, 8]
+    """
+
+    no, yes = tee(iterable)
+    no, yes = filterfalse(predicate, no), filter(predicate, yes)
+    return no, yes
+
+
+def _normalize_level(level):
+    # type: (Level) -> int | None
+    """Convert a logging level name or integer into a known logging level."""
+    if isinstance(level, int):
+        return level
+
+    try:
+        # pylint: disable-next=W0212
+        return logging._nameToLevel[level]
+    except KeyError:
+        return None
+    except TypeError:
+        raise TypeError(f"invalid logging level type for {level}")
+
+
+def _parse_levels(levels):
+    # type: (Level | list[Level] | tuple[Level, ...]) -> list[int]
+    """Convert one or more logging levels into a list of logging levels."""
+    if not isinstance(levels, (list, tuple)):
+        if not isinstance(levels, (int, str)):
+            raise TypeError("invalid logging level type")
+        levels = [levels]
+    return list(filter(_notnone, map(_normalize_level, levels)))
+
+
+class SphinxSuppressFilter(logging.Filter, metaclass=abc.ABCMeta):
+    def filter(self, record):
+        # type: (logging.LogRecord) -> bool
+        return not self.suppressed(record)
+
+    @abc.abstractmethod
+    def suppressed(self, record):
+        # type: (logging.LogRecord) -> bool
+        """Indicate whether *record* should be suppressed or not."""
+        pass
+
+
+class _All:
+    """Container simulating the universe."""
+
+    def __contains__(self, item):
+        # type: (Any) -> Literal[True]
+        return True
+
+
+_ALL = _All()
+
+
+class SphinxSuppressLogger(SphinxSuppressFilter):
+    r"""A filter suppressing logging records issued by a Sphinx logger."""
+
+    def __init__(self, name: str, levels=()):
+        """
+        Construct a :class:`SphinxSuppressLogger`.
+
+        :param name: The (real) logger name to suppress.
+        :type name: str
+        :param levels: Optional logging levels to suppress.
+        :type levels: bool | Level | list[Level] | tuple[Level, ...]
+        """
+
+        super().__init__(name)
+        if isinstance(levels, bool):
+            levels = _ALL if levels else []
+        else:
+            levels = _parse_levels(levels)
+
+        #: List of logging levels to suppress.
+        self.levels: list[int] = levels
+
+    def suppressed(self, record):
+        should_log = logging.Filter.filter(self, record)
+        return not should_log or record.levelno in self.levels
+
+
+class SphinxSuppressPatterns(SphinxSuppressFilter):
+    r"""A filter suppressing matching messages."""
+
+    def __init__(self, patterns=()):
+        """
+        Construct a :class:`SphinxSuppressPatterns`.
+
+        :param patterns: Optional logging messages (regex) to suppress.
+        :type patterns: list[str | re.Pattern]
+        """
+
+        super().__init__("")  # all loggers
+        #: Set of patterns to search.
+        self.patterns: set[re.Pattern] = set(map(re.compile, patterns))
+
+    def suppressed(self, record):
+        if not self.patterns:
+            return False
+
+        m = record.getMessage()
+        return any(p.search(m) for p in self.patterns)
+
+
+class SphinxSuppressRecord(SphinxSuppressLogger, SphinxSuppressPatterns):
+    r"""A filter suppressing matching messages by logger's name pattern."""
+
+    def __init__(self, name, levels=(), patterns=()):
+        """
+        Construct a :class:`SphinxSuppressRecord` filter.
+
+        :param name: A logger's name to suppress.
+        :type name: str
+        :param levels: Optional logging levels to suppress.
+        :type levels: bool | list[int]
+        :param patterns: Optional logging messages (regex) to suppress.
+        :type patterns: list[str | re.Pattern]
+        """
+
+        SphinxSuppressLogger.__init__(self, name, levels)
+        SphinxSuppressPatterns.__init__(self, patterns)
+
+    def suppressed(self, record):
+        return SphinxSuppressLogger.suppressed(self, record) and SphinxSuppressPatterns.suppressed(self, record)
+
+
+class _FiltersAdapter:
+    def __init__(self, config):
+        format_name = lambda name: f"{NAMESPACE}.{name}"
+
+        filters_by_prefix = {}
+        for name, levels in config.zeta_suppress_loggers.items():
+            prefix = format_name(name)
+            suppressor = SphinxSuppressLogger(prefix, levels)
+            filters_by_prefix.setdefault(prefix, []).append(suppressor)
+
+        suppress_records = config.zeta_suppress_records
+        groups, patterns = _partition(_is_pattern_like, suppress_records)
+        for group in groups:  # type: tuple[str, ...]
+            prefix = format_name(group[0])
+            suppressor = SphinxSuppressRecord(prefix, True, group[1:])
+            filters_by_prefix.setdefault(prefix, []).append(suppressor)
+
+        #: The filter to always add.
+        self._global_filter = SphinxSuppressPatterns(patterns)
+        #: The lists of filters to add, indexed by logger's prefix.
+        #:
+        #: The prefix always starts with :data:`sphinx.util.logging.NAMESPACE`,
+        #: followed by a dot.
+        self._filters_by_prefix = filters_by_prefix
+
+    def get_module_names(self):
+        # type: () -> Generator[str, None, None]
+        """
+        Yield the names of the modules that are expected to be altered.
+
+        Note that the ``NAMESPACE + '.'`` prefix added by Sphinx is removed.
+        """
+
+        prefix_len = len(NAMESPACE) + 1
+        for logger_name in self._filters_by_prefix:
+            yield logger_name[prefix_len:]
+
+    def get_filters(self, name):
+        """Yield the filters to add for the given logger's name.
+
+        :param name: The logger's name.
+        :type name: str
+        :return: The list of filters to add.
+        :rtype: collections.abc.Generator[SphinxSuppressFilter, None, None]
+
+        .. note:: The caller is responsible for adding the filters once.
+        """
+
+        for prefix, filters in self._filters_by_prefix.items():
+            if name.startswith(prefix):
+                yield from filters
+        yield self._global_filter
+
+
+_CACHE_ATTR_NAME = "_zeta_suppress_cache"
+
+
+def _mark_module(app, module_name):
+    # type: (Sphinx, str) -> None
+    """Mark a module name as being altered."""
+    getattr(app, _CACHE_ATTR_NAME).add(module_name)
+
+
+def _skip_module(app, module_name):
+    # type: (Sphinx, str) -> bool
+    """Check whether a named module should be skipped or not."""
+    return module_name in app.config.zeta_suppress_protect or module_name in getattr(app, _CACHE_ATTR_NAME)
+
+
+def _update_module(config, module, filters):
+    # type: (Config, ModuleType, _FiltersAdapter) -> None
+    """Update the module's loggers using the corresponding filters."""
+    adapters = inspect.getmembers(module, _is_sphinx_logger_adapter)
+    for _, adapter in adapters:
+        for f in filters.get_filters(adapter.logger.name):
+            # Since a logger may be imported from a non-marked module,
+            # we ensure that the filter is only added at most once.
+            if f not in adapter.logger.filters:
+                logger.debug("updating logger: %s", adapter.logger.name, type="sphinx-zeta-suppress", once=True)
+                adapter.logger.addFilter(f)
+
+
+@contextlib.contextmanager
+def _suppress_deprecation_warnings():
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        warnings.simplefilter("ignore", PendingDeprecationWarning)
+        yield
+
+
+def _setup_filters(app, module, filters):
+    """Alter the Sphinx loggers accessible in *module* and its submodules.
+
+    :param app: The current Sphinx application.
+    :type app: sphinx.application.Sphinx
+    :param module: The module to alter.
+    :type module: types.ModuleType
+    :param filters: A filters configuration adapter.
+    :type filters: _FiltersAdapter
+    """
+
+    if _skip_module(app, module.__name__):
+        logger.debug("skipping module: %s", module.__name__, type="sphinx-zeta-suppress", once=True)
+        return
+
+    _mark_module(app, module.__name__)
+    _update_module(app.config, module, filters)
+
+    if not hasattr(module, "__path__"):
+        return
+
+    # scan the submodules
+    mod_path, mod_prefix = module.__path__, module.__name__ + "."
+    with _suppress_deprecation_warnings():
+        for mod_info in pkgutil.walk_packages(mod_path, mod_prefix):
+            if _skip_module(app, mod_info.name):
+                logger.debug("skipping module: %s", mod_info.name, type="sphinx-zeta-suppress", once=True)
+                continue
+
+            try:
+                submodule = importlib.import_module(mod_info.name)
+            except ImportError as err:
+                logger.warning("cannot import module: %s", mod_info.name, exc_info=err, type="sphinx-zeta-suppress")
+                continue
+
+            _mark_module(app, mod_info.name)
+            _update_module(app, submodule, filters)
+
+
+# event handlers
+
+
+def install_suppress_handlers(app, config):
+    # type: (Sphinx, Config) -> None
+    """Event handler for :event:`config-inited`.
+
+    This handler is called twice, namely as the first and the last handlers
+    for :event:`config-inited` so that loggers emitting messages during the
+    initialization or loggers declared after :event:`config-inited` is fired
+    can be altered properly.
+    """
+
+    filters = _FiltersAdapter(config)
+
+    # scan the loaded extensions and alter them
+    for extension in app.extensions.values():  # type: Extension
+        module = extension.module
+        _setup_filters(app, module, filters)
+
+    # scan modules and alter them directly
+    with _suppress_deprecation_warnings():
+        for mod_name in filters.get_module_names():  # type: str
+            if not _skip_module(app, mod_name):
+                try:
+                    module = importlib.import_module(mod_name)
+                except Exception as err:
+                    msg = f"cannot import module {mod_name!r}"
+                    raise ExtensionError(msg, err, __name__)
+
+                _setup_filters(app, module, filters)
+
+
+def _create_temporary_cache(app, config):
+    # type: (Sphinx, Config) -> None
+    """Create a temporary attribute to hold the altered modules."""
+    if not hasattr(app, _CACHE_ATTR_NAME):
+        setattr(app, _CACHE_ATTR_NAME, set())
+
+
+def _delete_temporary_cache(app, config):
+    # type: (Sphinx, Config) -> None
+    """Delete the temporary attribute holding the altered modules."""
+    if hasattr(app, _CACHE_ATTR_NAME):
+        delattr(app, _CACHE_ATTR_NAME)
+
+
+def setup(app):
+    # type: (Sphinx) -> dict
+    app.add_config_value("zeta_suppress_loggers", {}, True)
+    app.add_config_value("zeta_suppress_protect", [], True)
+    app.add_config_value("zeta_suppress_records", [], True)
+    app.connect("config-inited", _create_temporary_cache, priority=-1)
+    # @contract: no logger emits a message before 'config-inited' is fired
+    app.connect("config-inited", install_suppress_handlers, priority=0)
+    # @contract: no extension is loaded after config-inited is fired
+    app.connect("config-inited", install_suppress_handlers, priority=1000)
+    app.connect("config-inited", _delete_temporary_cache, priority=1001)
+    return {"parallel_read_safe": True, "parallel_write_safe": True}
diff --git a/docs/user_guide/extending/tutorial01_custom_method.ipynb b/docs/user_guide/extending/tutorial01_custom_method.ipynb
index ffbe8456..da1e830f 100644
--- a/docs/user_guide/extending/tutorial01_custom_method.ipynb
+++ b/docs/user_guide/extending/tutorial01_custom_method.ipynb
@@ -416,7 +416,7 @@
     "import numpy as np\n",
     "\n",
     "from tempor.core.plugins import register_plugin\n",
-    "from tempor.methods.core import Params\n",
+    "from tempor.methods.core.params import Params\n",
     "from tempor.data import dataset, samples\n",
     "from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier\n",
     "\n",
diff --git a/docs/user_guide/usage/tutorial09_automl.ipynb b/docs/user_guide/usage/tutorial09_automl.ipynb
index 9e4a9e37..05b0ea92 100644
--- a/docs/user_guide/usage/tutorial09_automl.ipynb
+++ b/docs/user_guide/usage/tutorial09_automl.ipynb
@@ -128,7 +128,7 @@
     }
    ],
    "source": [
-    "from tempor.automl.params import IntegerParams, CategoricalParams\n",
+    "from tempor.methods.core.params import IntegerParams, CategoricalParams\n",
     "\n",
     "# Provide a custom hyperparameter space to search for each type of model.\n",
     "# NOTE: For the sake of speed of this example, we limit epochs to 2.\n",
@@ -588,7 +588,7 @@
     }
    ],
    "source": [
-    "from tempor.automl.params import IntegerParams, CategoricalParams\n",
+    "from tempor.methods.core.params import IntegerParams, CategoricalParams\n",
     "\n",
     "# Provide a custom hyperparameter space to search for each type of model.\n",
     "# These can be provided for the final (predictive) step of the pipeline.\n",
diff --git a/pyproject.toml b/pyproject.toml
index 71e4be6b..769e3c2f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -52,21 +52,18 @@ exclude_dirs = ["tests"]
 [tool.pydocstyle]
 convention = "google"
 add-ignore = [
-    "D100",
-    "D101",
-    "D102",
-    "D103",
-    "D104",
-    "D105",
-    "D106",
-    "D107",
+    # Disable especially pedantic checks:
+    "D101",  # Disable "Missing docstring in public class" as we take class docstrings from `__init__()`.
+    "D202",
     "D205",
+    "D402",
     "D403",
     "D415",
-    "D202"
 ]
 
-# Configure pyright.
+# Note: `pydoclint` is run via `flake8`.
+
+# Configure `pyright`.
 # May wish to add to pre-commit eventually.
 [tool.pyright]
 exclude = [
diff --git a/setup.cfg b/setup.cfg
index 84651ecb..b40f478b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -58,6 +58,7 @@ python_requires = >=3.7
 install_requires =
     clairvoyance2 >=0.0.2
     cloudpickle
+    cmaes  # Required for AutoML CMAES tuner.
     geomloss>=0.2.6
     hydra-core >=1.3
     hyperimpute >= 0.1.17
@@ -133,6 +134,8 @@ dev =
     isort
     mypy >= 1.0.0
     pylint
+    pydoclint
+    pydocstyle
     setuptools
     setuptools-scm
     tox
@@ -156,9 +159,9 @@ no_vcs = 1
 formats = bdist_wheel
 
 [flake8]
-# Some sane defaults for the code style checker flake8
+# Some sane defaults for the code style checker `flake8`.
 max_line_length = 120
-select = C,E,F,W,B,B950
+select = C,E,F,W,B,B950,DOC
 extend_ignore = E203, E501, W503
 # ^  Black-compatible
 #    E203 and W503 have edge cases handled by black
@@ -171,8 +174,12 @@ exclude =
     .eggs
     docs/conf.py
     .dev
+# `pydoclint` config (as `flake8` extension):
+style = google
+allow_init_docstring = True
+skip_checking_raises = True
 
-# Pylint compatibility with black and other config.
+# `pylint` compatibility with `black` and other config.
 # Source: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#pylint
 [pylint]
 max-line-length = 120
diff --git a/setup.py b/setup.py
index 2ff54327..b3a9b16e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,9 @@
-"""
-    Setup file for temporai.
-    Use setup.cfg to configure your project.
+"""Setup file for temporai.
+Use setup.cfg to configure your project.
 
-    This file was generated with PyScaffold 4.3.1.
-    PyScaffold helps you to put up the scaffold of your new Python project.
-    Learn more under: https://pyscaffold.org/
+This file was generated with PyScaffold 4.3.1.
+PyScaffold helps you to put up the scaffold of your new Python project.
+Learn more under: https://pyscaffold.org/
 """
 from setuptools import setup
 
diff --git a/src/hydra_plugins/__init__.py b/src/hydra_plugins/__init__.py
index e69de29b..06ac53e5 100644
--- a/src/hydra_plugins/__init__.py
+++ b/src/hydra_plugins/__init__.py
@@ -0,0 +1,1 @@
+"""Hydra integration."""
diff --git a/src/hydra_plugins/tempor_searchpath_plugin.py b/src/hydra_plugins/tempor_searchpath_plugin.py
index f941f799..54ea227d 100644
--- a/src/hydra_plugins/tempor_searchpath_plugin.py
+++ b/src/hydra_plugins/tempor_searchpath_plugin.py
@@ -1,7 +1,14 @@
+"""Tempor search path plugin for hydra."""
+
 import hydra.core.config_search_path
 import hydra.plugins.search_path_plugin
 
 
 class TemporSearchPathPlugin(hydra.plugins.search_path_plugin.SearchPathPlugin):
     def manipulate_search_path(self, search_path: hydra.core.config_search_path.ConfigSearchPath) -> None:
+        """Add tempor's config to the hydra search path.
+
+        Args:
+            search_path (hydra.core.config_search_path.ConfigSearchPath): The hydra search path.
+        """
         search_path.append(provider="tempor", path="pkg://tempor.config.conf")
diff --git a/src/tempor/__init__.py b/src/tempor/__init__.py
index a2069d38..f09bb975 100644
--- a/src/tempor/__init__.py
+++ b/src/tempor/__init__.py
@@ -1,3 +1,5 @@
+"""Initialization of the ``tempor`` library."""
+
 import sys
 import warnings
 
diff --git a/src/tempor/automl/__init__.py b/src/tempor/automl/__init__.py
index ec08afb5..39fb4841 100644
--- a/src/tempor/automl/__init__.py
+++ b/src/tempor/automl/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory with the AutoML functionality."""
+
 from ._types import OptimDirection
 
 __all__ = [
diff --git a/src/tempor/automl/_types.py b/src/tempor/automl/_types.py
index abb17c80..02285471 100644
--- a/src/tempor/automl/_types.py
+++ b/src/tempor/automl/_types.py
@@ -1,3 +1,5 @@
+"""Type definitions for AutoML."""
+
 from typing import Type, Union
 
 from typing_extensions import Literal
diff --git a/src/tempor/automl/params.py b/src/tempor/automl/params.py
deleted file mode 100644
index b0235c28..00000000
--- a/src/tempor/automl/params.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# TODO: Reorganize things so that this pass-through is not necessary.
-
-# pylint: disable=wildcard-import,unused-wildcard-import
-
-from tempor.methods.core._params import *  # noqa: F401,F403  # pragma: no cover
diff --git a/src/tempor/automl/pipeline_selector.py b/src/tempor/automl/pipeline_selector.py
index 867ccb8a..1f9438a0 100644
--- a/src/tempor/automl/pipeline_selector.py
+++ b/src/tempor/automl/pipeline_selector.py
@@ -1,3 +1,5 @@
+"""Module with pipeline selector helper class for AutoML."""
+
 from typing import Any, Dict, List, Optional, Tuple, Type
 
 import optuna
@@ -6,7 +8,7 @@
 from tempor.core import utils
 from tempor.core.types import PredictiveTaskType
 from tempor.methods.core import BaseEstimator, BasePredictor
-from tempor.methods.core._params import CategoricalParams, Params
+from tempor.methods.core.params import CategoricalParams, Params
 from tempor.methods.pipeline import PipelineBase, pipeline
 from tempor.methods.preprocessing.imputation import BaseImputer
 from tempor.methods.preprocessing.scaling import BaseScaler
@@ -140,11 +142,11 @@ def hyperparameter_space(
         Parameter names are customized to be able to differentiate pipeline stages (see ``format_hps_names``).
 
         Args:
-            args (Any):
+            *args (Any):
                 Arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
             override (Optional[List[Params]], optional):
                 Hyperparameter space override for the final predictive step of the pipeline. Defaults to `None`.
-            kwargs (Any):
+            **kwargs (Any):
                 Keyword arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
 
         Returns:
@@ -174,8 +176,12 @@ def sample_hyperparameters(
         """The customized ``sample_hyperparameters`` implementation. Uses the customized ``hyperparameter_space``.
 
         Args:
+            *args (Any):
+                Arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
             override (Optional[List[Params]], optional):
                 Hyperparameter space override for the final predictive step of the pipeline. Defaults to `None`.
+            **kwargs (Any):
+                Keyword arguments to be passed on to the ``hyperparameter_space`` methods of the pipeline steps.
 
         Returns:
             Dict[str, Any]: Sampled hyperparameters dictionary.
diff --git a/src/tempor/automl/seeker.py b/src/tempor/automl/seeker.py
index c05dd400..412a151e 100644
--- a/src/tempor/automl/seeker.py
+++ b/src/tempor/automl/seeker.py
@@ -18,7 +18,8 @@
 from tempor.data import data_typing
 from tempor.data.dataset import PredictiveDataset, TimeToEventAnalysisDataset
 from tempor.log import logger
-from tempor.methods.core import BasePredictor, Params
+from tempor.methods.core import BasePredictor
+from tempor.methods.core.params import Params
 
 from ._types import AutoMLCompatibleEstimator, OptimDirection
 from .pipeline_selector import (
@@ -129,9 +130,9 @@ def evaluation_callback_dispatch(
             terminated. Otherwise the exception will be ignored and a dummy value returned.
         silence_warnings (bool, optional):
             Whether to silence warnings raised. Defaults to `False`.
-        args (Any):
+        *args (Any):
             Positional arguments to pass to the estimator constructor.
-        kwargs (Any):
+        **kwargs (Any):
             Keyword arguments to pass to the estimator constructor.
 
     Returns:
@@ -268,6 +269,8 @@ def __init__(  # pylint: disable=unused-argument
             silence_warnings (bool, optional):
                 Whether to silence warnings raised. Some dependencies (e.g. `xgbse`) may circumvent this and raise
                 warnings regardless. Defaults to `False`.
+            **kwargs (Any):
+                Currently unused.
 
         Raises:
             ValueError: If incompatible / invalid input arguments have been passed.
@@ -552,7 +555,7 @@ def __init__(
                 See `~tempor.automl.seeker.BaseSeeker`.
             silence_warnings (bool, optional):
                 See `~tempor.automl.seeker.BaseSeeker`.
-            kwargs (Any):
+            **kwargs (Any):
                 See `~tempor.automl.seeker.BaseSeeker`.
         """
         estimator_defs = estimator_names
@@ -689,7 +692,7 @@ def __init__(
                 See `~tempor.automl.seeker.BaseSeeker`.
             silence_warnings (bool, optional):
                 See `~tempor.automl.seeker.BaseSeeker`.
-            kwargs (Any):
+            **kwargs (Any):
                 See `~tempor.automl.seeker.BaseSeeker`.
         """
         # Define estimator definitions:
diff --git a/src/tempor/automl/tuner.py b/src/tempor/automl/tuner.py
index bdb5822a..6b305908 100644
--- a/src/tempor/automl/tuner.py
+++ b/src/tempor/automl/tuner.py
@@ -11,8 +11,8 @@
 from tempor.core import pydantic_utils
 from tempor.data.dataset import PredictiveDataset
 from tempor.log import logger
-from tempor.methods.core import Params
 from tempor.methods.core._base_predictor import BasePredictor
+from tempor.methods.core.params import Params
 
 from ._types import AutoMLCompatibleEstimator, OptimDirection
 from .pipeline_selector import PipelineSelector
@@ -37,7 +37,18 @@ class EvaluationCallback(Protocol):
     def __call__(
         self, estimator: Type[BasePredictor], dataset: PredictiveDataset, *args: Any, **kwargs: Any
     ) -> float:  # pragma: no cover
-        ...
+        """Evaluation callback call.
+
+        Args:
+            estimator (Type[BasePredictor]): Any predictor.
+            dataset (PredictiveDataset): Any predictive dataset.
+            *args (Any): Any additional arguments.
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            float: Evaluation value/score.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
 
 class BaseTuner(abc.ABC):
@@ -56,6 +67,8 @@ def __init__(  # pylint: disable=unused-argument
                 Study name.
             direction (OptimDirection):
                 Optimization direction (`"minimize"` or `"maximize"`).
+            **kwargs (Any):
+                Currently unused.
         """
         self.study_name = study_name
         self.direction = direction
@@ -86,6 +99,8 @@ def tune(
                 If `True`, a trial will be run with default parameters (hyperparameters passed to ``__init__`` as an
                 empty dictionary). This will be returned as the zeroth item in ``scores`` and ``params``. If `False`,
                 this will be skipped. Defaults to `True`.
+            **kwargs (Any):
+                Currently unused.
 
         Returns:
             Tuple[List[float], List[Dict]]:
@@ -124,6 +139,8 @@ def __init__(
                 An `optuna` pruner (passed to `optuna.create_study`). Defaults to `None`.
             study_load_if_exists (bool, optional):
                 The `load_if_exists` parameter (passed to `optuna.create_study`). Defaults to `False`.
+            **kwargs (Any):
+                Currently unused.
         """
         super().__init__(
             study_name=study_name,
@@ -183,8 +200,11 @@ def tune(
                 this will be skipped. Defaults to `True`.
             optimize_kwargs (Optional[Dict[str, Any]], optional):
                 Keyword arguments to pass to ``study.optimize``. Defaults to `None`.
+            **kwargs (Any):
+                Currently unused.
 
-        Tuple[List[float], List[Dict]]:
+        Returns:
+            Tuple[List[float], List[Dict]]:
                 ``(scores, params)`` tuple, containing a list of scores for the tuning runs and a list of dictionaries\
                 containing the parameters for each corresponding tuning run.
         """
diff --git a/src/tempor/benchmarks/__init__.py b/src/tempor/benchmarks/__init__.py
index a957a26b..4d87b0a0 100644
--- a/src/tempor/benchmarks/__init__.py
+++ b/src/tempor/benchmarks/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory for benchmarking methods on datasets."""
+
 from .benchmark import benchmark_models, visualize_benchmark  # noqa: F401
 from .evaluation import (  # noqa: F401
     ClassifierSupportedMetric,
diff --git a/src/tempor/benchmarks/benchmark.py b/src/tempor/benchmarks/benchmark.py
index 4babe101..1d8147e1 100644
--- a/src/tempor/benchmarks/benchmark.py
+++ b/src/tempor/benchmarks/benchmark.py
@@ -1,3 +1,5 @@
+"""The main benchmarking module."""
+
 from typing import Any, Callable, Dict, List, Optional, Tuple
 
 import matplotlib.pyplot as plt
@@ -16,6 +18,7 @@
 
 
 def print_score(mean: pd.Series, std: pd.Series) -> pd.Series:
+    """Print the mean and standard deviation of a metric in a human-readable format."""
     with pd.option_context("mode.chained_assignment", None):  # pyright: ignore
         mean.loc[(mean < 1e-3) & (mean != 0)] = 1e-3
         std.loc[(std < 1e-3) & (std != 0)] = 1e-3
@@ -45,13 +48,13 @@ def benchmark_models(
             The options are any of `PredictiveTaskType`.
         tests (List[Tuple[str, Any]]):
             Tuples of form ``(test_name: str, plugin: BasePredictor/Pipeline)``
-        data (dataset.Dataset):
+        data (dataset.PredictiveDataset):
             The evaluation dataset to use for cross-validation.
         n_splits (int, optional):
             Number of splits used for cross-validation. Defaults to ``3``.
         random_state (int, optional):
             Random seed. Defaults to ``0``.
-        horizons (data_typing.TimeIndex, optional):
+        horizons (Optional[data_typing.TimeIndex], optional):
             Time horizons for making predictions, if applicable to the task.
         raise_exceptions (bool, optional):
             Whether to raise exceptions during evaluation. If `False`, the exceptions will be swallowed and the
@@ -135,6 +138,19 @@ def benchmark_models(
 
 @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
 def visualize_benchmark(results: Dict[str, pd.DataFrame], palette: str = "viridis", plot_block: bool = True) -> Any:
+    """Visualize the benchmarking results.
+
+    Args:
+        results (Dict[str, pd.DataFrame]):
+            The ``results`` dictionary returned by `benchmark_models`.
+        palette (str, optional):
+            `seaborn` color palette for the visualization. Defaults to ``"viridis"``.
+        plot_block (bool, optional):
+            Whether to block the execution flow by the generated `matplotlib` chart. Defaults to `True`.
+
+    Returns:
+        Any: The list of `matplotlib` axes objects with the generated plots.
+    """
     # Pre-format DF for plotting.
     for k, v in results.items():
         v["method"] = k
diff --git a/src/tempor/benchmarks/evaluation.py b/src/tempor/benchmarks/evaluation.py
index 90390b66..69241b35 100644
--- a/src/tempor/benchmarks/evaluation.py
+++ b/src/tempor/benchmarks/evaluation.py
@@ -1,3 +1,5 @@
+"""Module with helpers for evaluating the performance of the methods."""
+
 import copy
 import warnings
 from time import time
@@ -240,6 +242,16 @@ def __init__(
             self.metrics = metric
 
     def score_proba(self, y_test: np.ndarray, y_pred_proba: np.ndarray) -> Dict[str, float]:
+        """Return the evaluation metrics for the given predictions, where the predictions are of
+        predicted probabilities form.
+
+        Args:
+            y_test (np.ndarray): Test labels.
+            y_pred_proba (np.ndarray): Predicted probabilities.
+
+        Returns:
+            Dict[str, float]: Evaluation score.
+        """
         if y_test is None or y_pred_proba is None:
             raise ValueError("Invalid input for score_proba")
 
@@ -328,9 +340,29 @@ def score_proba(self, y_test: np.ndarray, y_pred_proba: np.ndarray) -> Dict[str,
         return results
 
     def roc_auc_score(self, y_test: np.ndarray, y_pred_proba: np.ndarray) -> float:
+        """Return the ROC AUC score for the given predictions, where the predictions are of
+        predicted probabilities form.
+
+        Args:
+            y_test (np.ndarray): Test labels.
+            y_pred_proba (np.ndarray): Predicted probabilities.
+
+        Returns:
+            float: ROC AUC score.
+        """
         return utils.evaluate_auc_multiclass(y_test, y_pred_proba)[0]
 
     def average_precision_score(self, y_test: np.ndarray, y_pred_proba: np.ndarray) -> float:
+        """Return the average precision score for the given predictions, where the predictions are of
+        predicted probabilities form.
+
+        Args:
+            y_test (np.ndarray): Test labels.
+            y_pred_proba (np.ndarray): Predicted probabilities.
+
+        Returns:
+            float: Average precision score.
+        """
         return utils.evaluate_auc_multiclass(y_test, y_pred_proba)[1]
 
 
@@ -349,7 +381,7 @@ def evaluate_prediction_oneoff_classifier(  # pylint: disable=unused-argument
     Args:
         estimator (Any):
             Baseline model to evaluate - must be unfitted.
-        data (dataset.Dataset):
+        data (dataset.PredictiveDataset):
             The dataset.
         n_splits (int, optional):
             Cross-validation folds. Defaults to ``3``.
@@ -361,6 +393,8 @@ def evaluate_prediction_oneoff_classifier(  # pylint: disable=unused-argument
             dataframe. Defaults to `False`.
         silence_warnings (bool, optional):
             Whether to silence warnings raised. Defaults to `False`.
+        **kwargs (Any):
+            Currently unused.
 
     Returns:
         pd.DataFrame:
@@ -438,7 +472,7 @@ def evaluate_prediction_oneoff_regressor(  # pylint: disable=unused-argument
     Args:
         estimator (Any):
             Baseline model to evaluate - must be unfitted.
-        data (dataset.Dataset):
+        data (dataset.PredictiveDataset):
             The dataset.
         n_splits (int, optional):
             Cross-validation folds. Defaults to ``3``.
@@ -450,6 +484,8 @@ def evaluate_prediction_oneoff_regressor(  # pylint: disable=unused-argument
             dataframe. Defaults to `False`.
         silence_warnings (bool, optional):
             Whether to silence warnings raised. Defaults to `False`.
+        **kwargs (Any):
+            Currently unused.
 
     Returns:
         pd.DataFrame:
@@ -523,6 +559,17 @@ def evaluate_prediction_oneoff_regressor(  # pylint: disable=unused-argument
 def compute_c_index(
     training_array_struct: np.ndarray, testing_array_struct: np.ndarray, predictions: np.ndarray, horizons: List[float]
 ) -> List[float]:
+    """Compute the IPCW concordance index.
+
+    Args:
+        training_array_struct (np.ndarray): Training data as a structured array.
+        testing_array_struct (np.ndarray): Testing data as a structured array.
+        predictions (np.ndarray): Predictions.
+        horizons (List[float]): Evaluation horizons.
+
+    Returns:
+        List[float]: List of metric values for each horizon.
+    """
     metrics: List[float] = []
     for horizon_idx, horizon_time in enumerate(horizons):
         predictions_at_horizon_time = predictions[:, horizon_idx, :].reshape((-1,))
@@ -536,6 +583,17 @@ def compute_c_index(
 def compute_brier_score(
     training_array_struct: np.ndarray, testing_array_struct: np.ndarray, predictions: np.ndarray, horizons: List[float]
 ) -> List[float]:
+    """Compute the time-dependent Brier score.
+
+    Args:
+        training_array_struct (np.ndarray): Training data as a structured array.
+        testing_array_struct (np.ndarray): Testing data as a structured array.
+        predictions (np.ndarray): Predictions.
+        horizons (List[float]): Evaluation horizons.
+
+    Returns:
+        List[float]: List of metric values for each horizon.
+    """
     predictions = predictions.reshape((predictions.shape[0], predictions.shape[1]))
     times, scores = tempor_metrics.brier_score(  # pylint: disable=unused-variable
         training_array_struct, testing_array_struct, predictions, horizons
@@ -614,6 +672,8 @@ def evaluate_time_to_event(  # pylint: disable=unused-argument
             dataframe. Defaults to `False`.
         silence_warnings (bool, optional):
             Whether to silence warnings raised. Defaults to `False`.
+        **kwargs (Any):
+            Currently unused.
 
     Returns:
         pd.DataFrame:
diff --git a/src/tempor/benchmarks/metrics.py b/src/tempor/benchmarks/metrics.py
index 2349b8ca..9f277be6 100644
--- a/src/tempor/benchmarks/metrics.py
+++ b/src/tempor/benchmarks/metrics.py
@@ -1,3 +1,5 @@
+"""Module with any additional metrics."""
+
 from typing import Any, Dict, Optional, Tuple, Union
 
 import numpy as np
@@ -24,7 +26,7 @@ def check_y_survival(y_or_event: np.ndarray, *args: Any, allow_all_censored: boo
             Structured array with two fields, or boolean array. If a structured array, it must contain the binary
             event indicator as first field, and time of event or time of censoring as second field. Otherwise, it is
             assumed that a boolean array representing the event indicator is passed.
-        args:
+        *args (Any):
             List of array-likes. Any number of array-like objects representing time information. Elements that are
             `None` are passed along in the return value.
         allow_all_censored (bool, optional):
@@ -204,7 +206,7 @@ def _compute_counts_truncated(
     Args:
         event (np.ndarray):
             Boolean event indicator.
-        time_start (np.ndarray):
+        time_enter (np.ndarray):
             Time when a subject entered the study.
         time_exit (np.ndarray):
             Time when a subject left the study due to an
@@ -269,21 +271,21 @@ def kaplan_meier_estimator(
     time_exit: Any,
     time_enter: Any = None,
     time_min: Optional[float] = None,
-    reverse: Optional[bool] = False,
+    reverse: bool = False,
 ) -> Tuple[np.ndarray, np.ndarray]:
     """Kaplan-Meier estimator of survival function. See [1] for further description.
 
     Args:
-        event:
+        event (Any):
             Array-like, ``shape = (n_samples,)``. Contains binary event indicators.
-        time_exit:
+        time_exit (Any):
             Array-like, ``shape = (n_samples,)``. Contains event/censoring times.
-        time_enter:
+        time_enter (Any):
             Array-like, ``shape = (n_samples,)``, optional. Contains time when each individual entered the study for
             left truncated survival data. Defaults to `None`.
-        time_min (float, optional):
+        time_min (Optional[float], optional):
             Compute estimator conditional on survival at least up to the specified time. Defaults to `None`.
-        reverse (bool, optional).
+        reverse (bool, optional):
             Whether to estimate the censoring distribution. When there are ties between times at which events are
             observed, then events come first and are subtracted from the denominator. Only available for
             right-censored data, i.e. ``time_enter`` must be None. Defaults to `False`.
@@ -336,10 +338,9 @@ def kaplan_meier_estimator(
 
 
 class SurvivalFunctionEstimator(sklearn.base.BaseEstimator):  # type: ignore [misc]
-    """Kaplan-Meier estimate of the survival function."""
-
     def __init__(self) -> None:
-        pass
+        """Kaplan-Meier estimate of the survival function."""
+        pass  # pylint: disable=unnecessary-pass
 
     def fit(self, y: np.ndarray) -> Self:
         """Estimate survival distribution from training data.
@@ -350,7 +351,7 @@ def fit(self, y: np.ndarray) -> Self:
                 as first field, and time of event or time of censoring as second field.
 
         Returns:
-            Self.
+            Self: Fitted estimator.
         """
         event, time = check_y_survival(y, allow_all_censored=True)  # pylint: disable=unbalanced-tuple-unpacking
 
@@ -413,7 +414,7 @@ def fit(self, y: np.ndarray) -> Self:
                 as first field, and time of event or time of censoring as second field.
 
         Returns:
-            Self.
+            Self: Fitted estimator.
         """
         event, time = check_y_survival(y)  # pylint: disable=unbalanced-tuple-unpacking
         if event.all():
@@ -690,13 +691,13 @@ def concordance_index_ipcw(
             Structured array, ``shape = (n_samples,)``.
             Survival times of test data. A structured array containing the binary event indicator as first field,
             and time of event or time of censoring as second field.
-        estimate:
+        estimate (Any):
             Array-like, ``shape = (n_samples,)``. Estimated risk of experiencing an event of test data.
-        tau (float, optional):
+        tau (Optional[float], optional):
             Truncation time. The survival function for the underlying censoring time distribution :math:`D` needs to be
             positive at ``tau``, i.e., ``tau`` should be chosen such that the probability of being censored after time
             ``tau`` is non-zero: :math:`P(D > \tau) > 0`. If `None`, no truncation is performed.
-        tied_tol (float, optional).
+        tied_tol (float, optional):
             The tolerance value for considering ties. If the absolute difference between risk scores is smaller or
             equal than ``tied_tol``, risk scores are considered tied. Defaults to ``1e-8``.
 
diff --git a/src/tempor/benchmarks/utils.py b/src/tempor/benchmarks/utils.py
index d2542de7..e4bf5931 100644
--- a/src/tempor/benchmarks/utils.py
+++ b/src/tempor/benchmarks/utils.py
@@ -1,3 +1,5 @@
+"""Any utilities for the ``benchmark`` package directory."""
+
 from typing import Tuple, cast
 
 import numpy as np
@@ -8,6 +10,16 @@
 
 
 def get_y_pred_proba_hlpr(y_pred_proba: np.ndarray, nclasses: int) -> np.ndarray:
+    """A helper utility for inferring the correct y_pred_proba for multiclass situations, specifically in the case
+    of binary classification. See source code for the specifics.
+
+    Args:
+        y_pred_proba (np.ndarray): Predicted probabilities.
+        nclasses (int): Number of classes.
+
+    Returns:
+        np.ndarray: The correctly inferred ``y_pred_proba``.
+    """
     if nclasses == 2:
         if len(y_pred_proba.shape) < 2:
             return y_pred_proba
@@ -66,9 +78,26 @@ def evaluate_auc_multiclass(
 
 
 def generate_score(metric: np.ndarray) -> Tuple[float, float]:
+    """Return score as mean and confidence interval using the 1.96 rule: ``(mean, 1.96 * std / sqrt(n))``.
+    See e.g. https://math.stackexchange.com/a/1572814.
+
+    Args:
+        metric (np.ndarray): Input metric.
+
+    Returns:
+        Tuple[float, float]: The score as ``(mean, confidence interval)``.
+    """
     percentile_val = 1.96
     return (float(np.mean(metric)), percentile_val * np.std(metric) / np.sqrt(len(metric)))
 
 
 def print_score(score: Tuple[float, float]) -> str:
+    """Print score as ``mean +/- range`` (3 decimal places).
+
+    Args:
+        score (Tuple[float, float]): The score to print as ``(mean, range)``.
+
+    Returns:
+        str: The formatted string.
+    """
     return str(round(score[0], 3)) + " +/- " + str(round(score[1], 3))
diff --git a/src/tempor/config/__init__.py b/src/tempor/config/__init__.py
index 306fabc2..3941e9a9 100644
--- a/src/tempor/config/__init__.py
+++ b/src/tempor/config/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory for TemporAI configuration."""
+
 import dataclasses
 import os
 import pathlib
@@ -27,18 +29,33 @@
 
 @dataclasses.dataclass
 class LoggingConfig:
+    """The configuration class for logging."""
+
     level: str = omegaconf.MISSING
+    """Logging level. One of: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, ``"ERROR"``, ``"CRITICAL"``."""
     diagnose: bool = omegaconf.MISSING
+    """Whether to use `loguru`'s ``diagnose`` setting for exceptions."""
     backtrace: bool = omegaconf.MISSING
+    """Whether to use `loguru`'s ``backtrace`` setting for exceptions."""
     file_log: bool = omegaconf.MISSING
+    """Whether to log to a file."""
 
 
 @dataclasses.dataclass
 class TemporConfig:
+    """The main configuration class for the TemporAI library."""
+
     logging: LoggingConfig
+    """Logging configuration."""
     working_directory: str = omegaconf.MISSING
+    """Working directory for the library. Can be set to a path string, ``"$PWD"``. or ``"~"``."""
 
     def get_working_dir(self) -> str:
+        """Get the working directory, with ``"$PWD"`` and ``"~"`` expanded.
+
+        Returns:
+            str: The working directory string.
+        """
         if self.working_directory.startswith("$PWD"):
             return self.working_directory.replace("$PWD", os.getcwd(), 1)
         elif self.working_directory.startswith("~"):
@@ -68,6 +85,11 @@ def get_working_dir(self) -> str:
 
 # pylint: disable=protected-access
 def get_config() -> TemporConfig:
+    """Get the current TemporAI configuration.
+
+    Returns:
+        TemporConfig: TemporAI configuration.
+    """
     return _this_module._config
 
 
@@ -87,6 +109,14 @@ def _load(loaded_config: omegaconf.DictConfig) -> TemporConfig:
 
 
 def load_yaml_file(path: Union[str, pathlib.Path]) -> TemporConfig:
+    """Load a YAML file as a `TemporConfig` object.
+
+    Args:
+        path (Union[str, pathlib.Path]): The path to the YAML file.
+
+    Returns:
+        TemporConfig: TemporAI configuration.
+    """
     loaded = OmegaConf.load(path)
     if TYPE_CHECKING:  # pragma: no cover
         assert isinstance(loaded, omegaconf.DictConfig)  # nosec B101
@@ -94,6 +124,14 @@ def load_yaml_file(path: Union[str, pathlib.Path]) -> TemporConfig:
 
 
 def load_dictconfig(config_node: omegaconf.DictConfig) -> TemporConfig:
+    """Load an `omegaconf.DictConfig` as a `TemporConfig` object.
+
+    Args:
+        config_node (omegaconf.DictConfig): The configuration ``DictConfig``.
+
+    Returns:
+        TemporConfig: TemporAI configuration.
+    """
     return _load(config_node)
 
 
@@ -102,6 +140,15 @@ def load_dictconfig(config_node: omegaconf.DictConfig) -> TemporConfig:
 
 
 def configure(new_config: Union[TemporConfig, omegaconf.DictConfig, str, pathlib.Path]) -> TemporConfig:
+    """Configure TemporAI with a new config.
+
+    Args:
+        new_config (Union[TemporConfig, omegaconf.DictConfig, str, pathlib.Path]):
+            The new configuration. Can be a ``TemporConfig`` object, a ``DictConfig`` object, or a path to a YAML file.
+
+    Returns:
+        TemporConfig: TemporAI configuration.
+    """
     if isinstance(new_config, (str, pathlib.Path)):
         _this_module._config = load_yaml_file(path=new_config)  # type: ignore
     elif isinstance(new_config, omegaconf.DictConfig):
diff --git a/src/tempor/config/conf/__init__.py b/src/tempor/config/conf/__init__.py
index e69de29b..a79635de 100644
--- a/src/tempor/config/conf/__init__.py
+++ b/src/tempor/config/conf/__init__.py
@@ -0,0 +1,1 @@
+"""Package directory to contain the default TemporAI configuration."""
diff --git a/src/tempor/config/conf/tempor/__init__.py b/src/tempor/config/conf/tempor/__init__.py
index e69de29b..4a75ba93 100644
--- a/src/tempor/config/conf/tempor/__init__.py
+++ b/src/tempor/config/conf/tempor/__init__.py
@@ -0,0 +1,1 @@
+"""Package subdirectory to contain the default TemporAI configuration."""
diff --git a/src/tempor/core/__init__.py b/src/tempor/core/__init__.py
index e69de29b..23668100 100644
--- a/src/tempor/core/__init__.py
+++ b/src/tempor/core/__init__.py
@@ -0,0 +1,1 @@
+"""Package directory for TemporAI core functionality."""
diff --git a/src/tempor/core/plugins/__init__.py b/src/tempor/core/plugins/__init__.py
index 402842ab..be6cb1ed 100644
--- a/src/tempor/core/plugins/__init__.py
+++ b/src/tempor/core/plugins/__init__.py
@@ -1,3 +1,5 @@
+"""This module contains the core plugin functionality of TemporAI."""
+
 import glob
 import importlib
 import importlib.abc
@@ -35,13 +37,13 @@ def parse_plugin_type(
     as that is a reserved value.
 
     Args:
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg):
+        plugin_type (plugin_typing.PluginTypeArg):
             Plugin type.
-        must_be_one_of (List[str]):
+        must_be_one_of (Optional[List[str]]):
             List of plugin types that ``plugin_type`` must be one of.
 
     Returns:
-        ~tempor.core.plugin.plugin_typing.PluginTypeArg:
+        plugin_typing.PluginTypeArg:
             Default plugin type if ``plugin_type`` is ``None``, otherwise ``plugin_type``.
     """
     if plugin_type == plugin_typing.ALL_PLUGIN_TYPES_INDICATOR:
@@ -60,10 +62,9 @@ def create_fqn(
     `[plugin_type].category` respectively.
 
     Args:
-        suffix (Union[~tempor.core.plugin.plugin_typing.PluginCategory, \
-            ~tempor.core.plugin.plugin_typing.PluginFullName]):
+        suffix (Union[plugin_typing.PluginCategory, plugin_typing.PluginFullName]):
             Plugin category or plugin full name.
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg):
+        plugin_type (plugin_typing.PluginTypeArg):
             Plugin type.
 
     Returns:
@@ -80,13 +81,13 @@ def filter_list_by_plugin_type(
     """Filter a list of plugin FQNs by plugin type.
 
     Args:
-        lst (List[~tempor.core.plugin.plugin_typing._PluginFqn]):
+        lst (List[plugin_typing._PluginFqn]):
             List of plugin FQNs.
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg):
+        plugin_type (plugin_typing.PluginTypeArg):
             Plugin type.
 
     Returns:
-        List[~tempor.core.plugin.plugin_typing.PluginFullName]:
+        List[plugin_typing.PluginFullName]:
             Filtered list which will only include FQNs with the specified ``plugin_type``.
     """
     return [x for x in lst if x.split(".")[0] == f"[{plugin_type}]"]
@@ -98,13 +99,13 @@ def filter_dict_by_plugin_type(
     """Filter a dictionary with plugin FQN keys by plugin type.
 
     Args:
-        d (Dict[~tempor.core.plugin.plugin_typing._PluginFqn, Any]):
+        d (Dict[plugin_typing._PluginFqn, Any]):
             Dictionary to filter.
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg):
+        plugin_type (plugin_typing.PluginTypeArg):
             Plugin type.
 
     Returns:
-        Dict[~tempor.core.plugin.plugin_typing.PluginFullName, Any]:
+        Dict[plugin_typing.PluginFullName, Any]:
             Filtered dictionary which will only include items where FQN keys match the specified ``plugin_type``.
     """
     return {k: v for k, v in d.items() if k.split(".")[0] == f"[{plugin_type}]"}
@@ -122,7 +123,7 @@ def _parse_fqn_format(fqn: str) -> Tuple[plugin_typing.PluginTypeArg, str]:
             Raised if the FQN is of incorrect format, that is, doesn't begin with ``[<plugin_type>].<...>``.
 
     Returns:
-        Tuple[~tempor.core.plugin.plugin_typing.PluginTypeArg, str]:
+        Tuple[plugin_typing.PluginTypeArg, str]:
             Plugin type, remainder (``category`` or ``category.name``).
     """
     first_element = fqn.split(".")[0]
@@ -139,11 +140,11 @@ def remove_plugin_type_from_fqn(
     """Remove the plugin type part of a plugin FQN or category FQN.
 
     Args:
-        fqn (Union[~tempor.core.plugin.plugin_typing._PluginCategoryFqn, ~tempor.core.plugin.plugin_typing._PluginFqn]):
+        fqn (Union[plugin_typing._PluginCategoryFqn, plugin_typing._PluginFqn]):
             Plugin FQN of plugin category FQN.
 
     Returns:
-        Union[~tempor.core.plugin.plugin_typing.PluginCategory, ~tempor.core.plugin.plugin_typing.PluginFullName]:
+        Union[plugin_typing.PluginCategory, plugin_typing.PluginFullName]:
             The FQN with the plugin type part removed.
     """
     _, remainder = _parse_fqn_format(fqn)
@@ -156,11 +157,11 @@ def get_plugin_type_from_fqn(
     """Get the plugin type part of a plugin FQN or category FQN.
 
     Args:
-        fqn (Union[~tempor.core.plugin.plugin_typing._PluginCategoryFqn, ~tempor.core.plugin.plugin_typing._PluginFqn]):
+        fqn (Union[plugin_typing._PluginCategoryFqn, plugin_typing._PluginFqn]):
             Plugin FQN of plugin category FQN.
 
     Returns:
-        ~tempor.core.plugin.plugin_typing.PluginTypeArg: The plugin type.
+        plugin_typing.PluginTypeArg: The plugin type.
     """
     plugin_type, _ = _parse_fqn_format(fqn)
     return plugin_type
@@ -170,8 +171,6 @@ def get_plugin_type_from_fqn(
 
 
 class Plugin:
-    """The base class that all plugins must inherit from."""
-
     name: ClassVar[plugin_typing.PluginName] = PLUGIN_NAME_NOT_SET
     """Plugin name, such as ``'my_nn_classifier'``. Must be set by the plugin class using ``@register_plugin``."""
     category: ClassVar[plugin_typing.PluginCategory] = PLUGIN_CATEGORY_NOT_SET
@@ -183,13 +182,26 @@ class Plugin:
     else will set the default plugin type.
     """
 
+    def __init__(self) -> None:
+        """The base class that all plugins must inherit from."""
+        if self.name == PLUGIN_NAME_NOT_SET:
+            raise ValueError(f"Plugin {self.__class__.__name__} `name` was not set, use @{register_plugin.__name__}")
+        if self.category == PLUGIN_CATEGORY_NOT_SET:
+            raise ValueError(
+                f"Plugin {self.__class__.__name__} `category` was not set, use @{register_plugin.__name__}"
+            )
+        if self.plugin_type == PLUGIN_TYPE_NOT_SET:
+            raise ValueError(
+                f"Plugin {self.__class__.__name__} `plugin_type` was not set, use @{register_plugin.__name__}"
+            )
+
     @classmethod
     def full_name(cls) -> plugin_typing.PluginFullName:
         """The full name of the plugin with its category: ``category.subcategory.name``.
         There may be 0 or more subcategories.
 
         Returns:
-            ~tempor.core.plugin.plugin_typing.PluginFullName: Plugin full name.
+            plugin_typing.PluginFullName: Plugin full name.
         """
         return f"{cls.category}.{cls.name}"
 
@@ -198,7 +210,7 @@ def _fqn(cls) -> plugin_typing._PluginFqn:
         """The fully-qualified name of the plugin with its plugin type: ``[plugin_type].category.subcategory.name``
 
         Returns:
-            ~tempor.core.plugin.plugin_typing._PluginFqn: Plugin fully-qualified name.
+            plugin_typing._PluginFqn: Plugin fully-qualified name.
         """
         return f"{create_fqn(cls.category, cls.plugin_type)}.{cls.name}"
 
@@ -207,22 +219,10 @@ def _category_fqn(cls) -> plugin_typing._PluginCategoryFqn:
         """The fully-qualified name of the plugin's category: ``[plugin_type].category.subcategory``
 
         Returns:
-            ~tempor.core.plugin.plugin_typing._PluginCategoryFqn: Plugin category fully-qualified name.
+            plugin_typing._PluginCategoryFqn: Plugin category fully-qualified name.
         """
         return f"{create_fqn(cls.category, cls.plugin_type)}"
 
-    def __init__(self) -> None:
-        if self.name == PLUGIN_NAME_NOT_SET:
-            raise ValueError(f"Plugin {self.__class__.__name__} `name` was not set, use @{register_plugin.__name__}")
-        if self.category == PLUGIN_CATEGORY_NOT_SET:
-            raise ValueError(
-                f"Plugin {self.__class__.__name__} `category` was not set, use @{register_plugin.__name__}"
-            )
-        if self.plugin_type == PLUGIN_TYPE_NOT_SET:
-            raise ValueError(
-                f"Plugin {self.__class__.__name__} `plugin_type` was not set, use @{register_plugin.__name__}"
-            )
-
 
 # Important dicts that store plugin information:
 PLUGIN_CATEGORY_REGISTRY: Dict[plugin_typing._PluginCategoryFqn, Type[Plugin]] = dict()
@@ -241,13 +241,13 @@ def register_plugin_category(
     will be used.
 
     Args:
-        category (~tempor.core.plugin.plugin_typing.PluginCategory):
+        category (plugin_typing.PluginCategory):
             Plugin category, dot-separated, with optional subcategories, \
             such as ``'prediction.one_off.classification'``.
         expected_class (Type):
             The expected plugin class for this category. The plugin class must be a subclass of \
             this class. Note that this class must itself be a subclass of ``Plugin``.
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+        plugin_type (plugin_typing.PluginTypeArg, optional):
             Plugin type to register the category under. Different plugin types should be used to indicate different \
             domains of your code (e.g. methods vs metrics vs datasets). Defaults to `None`.
 
@@ -285,12 +285,15 @@ def register_plugin(
     Args:
         name (str):
             Plugin name, such as ``'my_nn_classifier'``.
-        category (~tempor.core.plugin.plugin_typing.PluginCategory):
+        category (plugin_typing.PluginCategory):
             Plugin category, dot-separated, with optional subcategories, such as \
             ``'prediction.one_off.classification'``.
-        plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+        plugin_type (plugin_typing.PluginTypeArg, optional):
             Plugin type of the category. If left as `None`, default plugin type is assumed. ``plugin_type`` must \
             correctly correspond to the ``category`` being specified. Defaults to `None`.
+
+    Returns:
+        Callable[[Callable[P, T]], Callable[P, T]]: Decorated class.
     """
 
     def _class_decorator(cls: Callable[P, T]) -> Callable[P, T]:
@@ -360,11 +363,17 @@ def _class_decorator(cls: Callable[P, T]) -> Callable[P, T]:
     return _class_decorator
 
 
+ListCategoriesReturn = Union[
+    Dict[plugin_typing.PluginFullName, Type[Plugin]],
+    Dict[plugin_typing.PluginType, Dict[plugin_typing.PluginFullName, Type[Plugin]]],
+]
+"""Type alias for the return type of ``list_categories``."""
+
+
 # TODO: Consider whether to enforce common base class across plugin_type/category.
 class PluginLoader:
-    """A class to load plugins. Provides functionality to list and get plugins."""
-
     def __init__(self) -> None:
+        """A class to load plugins. Provides functionality to list and get plugins."""
         self._refresh()
 
     def _refresh(self) -> None:
@@ -421,7 +430,7 @@ def list(self, plugin_type: plugin_typing.PluginTypeArg = None) -> Dict:
         with plugin type keys.
 
         Args:
-            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type for which to list. Use default category if `None`. Defaults to `None`.
 
         Returns:
@@ -445,12 +454,11 @@ def list_full_names(
         with plugin type keys.
 
         Args:
-            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type for which to list. Use default category if `None`. Defaults to `None`.
 
         Returns:
-            Union[List[~tempor.core.plugin.plugin_typing.PluginFullName], \
-            Dict[str, List[~tempor.core.plugin.plugin_typing.PluginFullName]]]:
+            Union[List[plugin_typing.PluginFullName], Dict[str, List[plugin_typing.PluginFullName]]]:
                 A list as described above (``List[PluginFullName]``) if ``plugin_type`` is not ``"all"``. \
                 Otherwise a nested dictionary with plugin type keys and such lists as values \
                 (``Dict[str, List[PluginFullName]]]``).
@@ -470,7 +478,7 @@ def list_classes(self, plugin_type: plugin_typing.PluginTypeArg = None) -> Dict:
         with plugin type keys.
 
         Args:
-            plugin_type (PluginType, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type for which to list. Use default category if `None`. Defaults to `None`.
 
         Returns:
@@ -486,12 +494,7 @@ def _list_categories(
         categories_filtered_by_type = filter_dict_by_plugin_type(d=PLUGIN_CATEGORY_REGISTRY, plugin_type=plugin_type)
         return {remove_plugin_type_from_fqn(k): v for k, v in categories_filtered_by_type.items()}
 
-    def list_categories(
-        self, plugin_type: plugin_typing.PluginTypeArg = None
-    ) -> Union[
-        Dict[plugin_typing.PluginFullName, Type[Plugin]],
-        Dict[plugin_typing.PluginType, Dict[plugin_typing.PluginFullName, Type[Plugin]]],
-    ]:
+    def list_categories(self, plugin_type: plugin_typing.PluginTypeArg = None) -> ListCategoriesReturn:
         """List all plugin categories of ``plugin_type`` as a dictionary, where the keys are the plugin category names
         (including optional subcategories) and the values are the **expected plugin classes** for that category.
 
@@ -499,13 +502,11 @@ def list_categories(
         with plugin type keys.
 
         Args:
-            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type for which to list. Use default category if `None`. Defaults to `None`.
 
         Returns:
-            Union[Dict[~tempor.core.plugin.plugin_typing.PluginFullName, Type[Plugin]], \
-            Dict[~tempor.core.plugin.plugin_typing.PluginType, \
-            Dict[~tempor.core.plugin.plugin_typing.PluginFullName, Type[Plugin]]]]:
+            ListCategoriesReturn:
                 A dictionary as described above (``Dict[PluginFullName, Type[Plugin]]``) if ``plugin_type`` is \
                 not ``"all"``. Otherwise a nested dictionary with plugin type keys and such dictionaries as values \
                 (``Dict[PluginType, Dict[PluginFullName, Type[Plugin]]]``).
@@ -516,7 +517,7 @@ def list_plugin_types(self) -> List[plugin_typing.PluginType]:
         """List all plugin types.
 
         Returns:
-            List[str]: A list of plugin types.
+            List[plugin_typing.PluginType]: A list of plugin types.
         """
         self._refresh()
         return core_utils.unique_in_order_of_appearance(
@@ -565,7 +566,7 @@ def get(  # type: ignore [misc]
     ) -> Type:
         ...  # pragma: co cover
 
-    def get(self, name: plugin_typing.PluginFullName, *args: Any, **kwargs: Any) -> Any:
+    def get(self, name: plugin_typing.PluginFullName, *args: Any, **kwargs: Any) -> Any:  # noqa: DOC102, DOC103
         """Get a plugin by its full name (including category, i.e. of form
         ``'my_category.my_subcategory.my_plugin'``). Use ``*args`` and ``**kwargs`` to pass arguments to
         the plugin initializer. The returned object is an instance of the plugin class. If the plugin is not of the
@@ -598,14 +599,14 @@ def get(self, name: plugin_typing.PluginFullName, *args: Any, **kwargs: Any) ->
             )
 
         Args:
-            name (~tempor.core.plugin.plugin_typing.PluginFullName):
+            name (plugin_typing.PluginFullName):
                 Plugin full name including all (sub)categories, of form ``'my_category.my_subcategory.my_plugin'``
-            args (Any):
+            *args (Any):
                 Arguments to pass to the plugin initializer.
-            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type. If left as `None`, default plugin type is assumed. ``plugin_type`` must correctly \
                 correspond to the category implied by plugin full name. Defaults to `None`.
-            kwargs (Any):
+            **kwargs (Any):
                 Keyword arguments to pass to the plugin initializer.
 
         Returns:
@@ -624,9 +625,9 @@ def get_class(self, name: plugin_typing.PluginFullName, plugin_type: plugin_typi
         ``plugin_type``.
 
         Args:
-            name (~tempor.core.plugin.plugin_typing.PluginFullName):
+            name (plugin_typing.PluginFullName):
                 Plugin full name including all (sub)categories, of form ``'my_category.my_subcategory.my_plugin'``
-            plugin_type (~tempor.core.plugin.plugin_typing.PluginTypeArg, optional):
+            plugin_type (plugin_typing.PluginTypeArg, optional):
                 Plugin type. If left as `None`, default plugin type is assumed. ``plugin_type`` must correctly \
                 correspond to the category implied by plugin full name. Defaults to `None`.
 
diff --git a/src/tempor/core/plugins/plugin_typing.py b/src/tempor/core/plugins/plugin_typing.py
index 644fd6b8..47486832 100644
--- a/src/tempor/core/plugins/plugin_typing.py
+++ b/src/tempor/core/plugins/plugin_typing.py
@@ -1,3 +1,5 @@
+"""Typing definitions for plugin core functionality."""
+
 from typing import Union
 
 from typing_extensions import Literal, get_args
diff --git a/src/tempor/core/pydantic_utils.py b/src/tempor/core/pydantic_utils.py
index 4ebc98cb..73f06575 100644
--- a/src/tempor/core/pydantic_utils.py
+++ b/src/tempor/core/pydantic_utils.py
@@ -1,3 +1,5 @@
+"""Module for `pydantic`-related utilities."""
+
 from typing import Any, Callable, Type, TypeVar, cast
 
 import pydantic
@@ -21,6 +23,14 @@
 
 
 def is_pydantic_dataclass(cls: Type) -> bool:
+    """A helper function to check if a class is a `pydantic` dataclass.
+
+    Args:
+        cls (Type): Class.
+
+    Returns:
+        bool: Whether the class is a `pydantic` dataclass.
+    """
     if Version(pydantic.__version__) < Version("2.0.0"):  # pragma: no cover
         return hasattr(cls, "__dataclass__")
     else:
@@ -79,10 +89,14 @@ def validate_arguments(*args: Any, **kwargs: Any) -> Callable[[Callable[P, T]],
     - https://stackoverflow.com/a/74080156
     - https://docs.python.org/3/library/typing.html#typing.ParamSpec
 
+    Args:
+        *args (Any): Positional arguments to pass to ``pydantic.validate_arguments``.
+        **kwargs (Any): Keyword arguments to pass to ``pydantic.validate_arguments``.
+
     Returns:
         Callable[[Callable[P, T]], Callable[P, T]]: The updated ``pydantic.validate_arguments`` decorator.
     """
     return cast(
-        Callable[P, T],  # type: ignore [valid-type, misc]
+        Callable[P, T],  # type: ignore [valid-type]
         pydantic.validate_arguments(*args, **kwargs),  # type: ignore [operator]
     )
diff --git a/src/tempor/core/types.py b/src/tempor/core/types.py
index 0f89c706..1431af64 100644
--- a/src/tempor/core/types.py
+++ b/src/tempor/core/types.py
@@ -1,3 +1,5 @@
+"""Type definitions for TemporAI core."""
+
 from typing_extensions import Literal
 
 PredictiveTaskType = Literal[
diff --git a/src/tempor/core/utils.py b/src/tempor/core/utils.py
index 9b1d9c4b..93f1a84f 100644
--- a/src/tempor/core/utils.py
+++ b/src/tempor/core/utils.py
@@ -1,10 +1,21 @@
+"""Utility functions for TemporAI core."""
+
 from typing import Any, Dict, Iterable, List, Tuple, Type
 
 from typing_extensions import Literal, get_args
 
 
 def get_class_full_name(o: object) -> str:
-    # See: https://stackoverflow.com/a/2020083
+    """Get the full name of a class.
+
+    See: https://stackoverflow.com/a/2020083.
+
+    Args:
+        o (object): The object to get the class full name of.
+
+    Returns:
+        str: The full name of the class.
+    """
     class_ = o.__class__
     module = class_.__module__
     if module == "builtins":
@@ -19,13 +30,31 @@ def get_class_full_name(o: object) -> str:
 
 class RichReprStrPassthrough:
     def __init__(self, string: str) -> None:
+        """A pass-through class for `rich` ``__repr__`` strings. Yields the ``string`` in its ``__repr__``.
+
+        Args:
+            string (str): The string to pass through.
+        """
         self.string = string
 
     def __repr__(self) -> str:
+        """The ``__repr__`` method, will simply return the ``string`` provided at initialization.
+
+        Returns:
+            str: String to return.
+        """
         return self.string
 
 
 def is_iterable(o: object) -> bool:
+    """Check if an object is an iterable.
+
+    Args:
+        o (object): The object to check.
+
+    Returns:
+        bool: Whether the object is an iterable.
+    """
     is_iterable_ = True
     try:
         iter(o)  # type: ignore[call-overload]
@@ -37,6 +66,17 @@ def is_iterable(o: object) -> bool:
 def ensure_literal_matches_dict_keys(
     literal: Any, d: Dict[str, Any], literal_name: str = "literal", dict_name: str = "dictionary"
 ) -> None:
+    """Check that the args of a literal match the keys of a dictionary.
+
+    Args:
+        literal (Any): A literal.
+        d (Dict[str, Any]): A dictionary.
+        literal_name (str, optional): The name of the literal, for exception description. Defaults to ``"literal"``.
+        dict_name (str, optional): The name of the dictionary, for exception description. Defaults to ``"dictionary"``.
+
+    Raises:
+        TypeError: Raised if the args of the literal do not match the keys of the dictionary.
+    """
     lits = set(get_args(literal))
     keys = set(d.keys())
     if lits != keys:
@@ -47,6 +87,9 @@ def ensure_literal_matches_dict_keys(
 
 
 PreferArgOrKwarg = Literal["arg", "kwarg", "exception"]
+"""Literal type for ``prefer`` argument in ``get_from_args_or_kwargs``.
+One of ``"arg"``, ``"kwarg"``, or ``"exception"``.
+"""
 
 
 def get_from_args_or_kwargs(
diff --git a/src/tempor/data/__init__.py b/src/tempor/data/__init__.py
index e69de29b..6d8892da 100644
--- a/src/tempor/data/__init__.py
+++ b/src/tempor/data/__init__.py
@@ -0,0 +1,1 @@
+"""Package directory for data format and related utilities."""
diff --git a/src/tempor/data/clv2conv.py b/src/tempor/data/clv2conv.py
index 0244b81c..52c2a30d 100644
--- a/src/tempor/data/clv2conv.py
+++ b/src/tempor/data/clv2conv.py
@@ -1,4 +1,5 @@
 """Utilities for converting to and from ``clairvoyance2`` datasets."""
+
 from typing import List, Type
 
 import pandas as pd
@@ -32,6 +33,14 @@ def _from_clv2_event(df: pd.DataFrame) -> pd.DataFrame:
 
 
 def clairvoyance2_dataset_to_tempor_dataset(data: Clairvoyance2Dataset) -> dataset.BaseDataset:
+    """A utility function to convert a ``clairvoyance2`` dataset to a TemporAI dataset.
+
+    Args:
+        data (Clairvoyance2Dataset): The ``clairvoyance2`` dataset to convert.
+
+    Returns:
+        dataset.BaseDataset: The converted dataset.
+    """
     if (
         data.temporal_targets is None
         and data.temporal_treatments is None
@@ -142,6 +151,14 @@ def _to_clv2_event(s: samples.EventSamples) -> pd.DataFrame:
 
 
 def tempor_dataset_to_clairvoyance2_dataset(data: dataset.BaseDataset) -> Clairvoyance2Dataset:
+    """A utility function to convert a TemporAI dataset to a ``clairvoyance2`` dataset.
+
+    Args:
+        data (dataset.BaseDataset): The TemporAI dataset to convert.
+
+    Returns:
+        Clairvoyance2Dataset: The converted dataset.
+    """
     if isinstance(data, dataset.OneOffPredictionDataset):
         raise ValueError(
             "Cannot convert a `OneOffPredictionDataset` to a clairvoyance2 dataset, as this setting is not supported"
diff --git a/src/tempor/data/dataset.py b/src/tempor/data/dataset.py
index 9cdb5e27..b69bdd7c 100644
--- a/src/tempor/data/dataset.py
+++ b/src/tempor/data/dataset.py
@@ -1,3 +1,5 @@
+"""Module defining the TemporAI dataset concept in :class:`BaseDataset` and its derived classes."""
+
 # pylint: disable=unnecessary-ellipsis
 
 import abc
@@ -77,20 +79,20 @@ def __init__(
         See also tutorial ``tutorials/tutorial01_data_format.ipynb`` for examples of use.
 
         Args:
-            time_series (numpy.ndarray | pandas.DataFrame):
+            time_series (data_typing.DataContainer):
                 Data representing time series covariates of the samples. Will be initialized as `TimeSeriesSamples`.
-            static (numpy.ndarray | pandas.DataFrame, optional):
+            static (Optional[data_typing.DataContainer], optional):
                 Data representing static covariates of the samples. Will be initialized as `StaticSamples`.
                 Defaults to `None`.
-            targets (numpy.ndarray | pandas.DataFrame, optional):
+            targets (Optional[data_typing.DataContainer], optional):
                 Data representing target (outcome) feature(s) of the samples. Will be initialized as
                 ``{TimeSeries,Static,Event}Samples`` depending on problem setting in the derived class.
                 Defaults to `None`.
-            treatments (numpy.ndarray | pandas.DataFrame, optional):
+            treatments (Optional[data_typing.DataContainer], optional):
                 Data representing treatment (intervention) feature(s) of the samples. Will be initialized as
                 ``{TimeSeries,Static,Event}Samples`` depending on problem setting in the derived class.
                 Defaults to `None`.
-            kwargs (Any):
+            **kwargs (Any):
                 Additional keyword arguments to be passed to the derived class's ``_init_predictive`` method.
         """
         self._time_series = samples.TimeSeriesSamples(time_series)
@@ -101,6 +103,11 @@ def __init__(
         self.validate()
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "time_series", RichReprStrPassthrough(self.time_series.short_repr())
         if self.static is not None:
             yield "static", RichReprStrPassthrough(self.static.short_repr())
@@ -108,6 +115,11 @@ def __rich_repr__(self) -> Generator:
             yield "predictive", self.predictive
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return rich.pretty.pretty_repr(self)
 
     @abc.abstractmethod
@@ -122,14 +134,29 @@ def _init_predictive(
 
     @property
     def has_static(self) -> bool:
+        """A property returning whether the dataset has static data.
+
+        Returns:
+            bool: Whether the dataset has static data.
+        """
         return self.static is not None
 
     @property
     def has_predictive_data(self) -> bool:
+        """A property returning whether the dataset has predictive data (``targets`` or ``treatments``).
+
+        Returns:
+            bool: Whether the dataset has predictive data.
+        """
         return self.predictive is not None
 
     @property
     def predictive_task(self) -> Union[data_typing.PredictiveTask, None]:
+        """A property returning the predictive task of the dataset (or `None`).
+
+        Returns:
+            Union[data_typing.PredictiveTask, None]: The predictive task of the dataset.
+        """
         if self.predictive is not None:
             return self.predictive.predictive_task
         else:
@@ -149,6 +176,11 @@ def _validate(self) -> None:  # pragma: no cover
 
     @property
     def time_series(self) -> samples.TimeSeriesSamples:
+        """The property containing the time series covariates of the dataset.
+
+        Returns:
+            samples.TimeSeriesSamples: The time series covariates of the dataset.
+        """
         return self._time_series
 
     @time_series.setter
@@ -158,6 +190,11 @@ def time_series(self, value: samples.TimeSeriesSamples) -> None:
 
     @property
     def static(self) -> Optional[samples.StaticSamples]:
+        """The property containing the static covariates of the dataset.
+
+        Returns:
+            Optional[samples.StaticSamples]: The static covariates of the dataset.
+        """
         return self._static
 
     @static.setter
@@ -172,9 +209,22 @@ def fit_ready(self) -> bool:  # pragma: no cover
         ...
 
     def __len__(self) -> int:
+        """The dataset length, which is the number of samples.
+
+        Returns:
+            int: Dataset length.
+        """
         return self.time_series.num_samples
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
+        """Return a subset of the dataset.
+
+        Args:
+            key (data_typing.GetItemKey): The key to index the dataset with.
+
+        Returns:
+            Self: The subset of the dataset.
+        """
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
         new_dataset = self.__class__(
             time_series=self.time_series[key_].dataframe(),  # pyright: ignore
@@ -206,6 +256,18 @@ def train_test_split(
         The arguments ``test_size`` ... ``stratify`` are passed to `sklearn.model_selection.train_test_split` to
         generate the split.
 
+        Args:
+            test_size (Optional[float], optional):
+                Passed to `sklearn.model_selection.train_test_split`. Defaults to `None`.
+            train_size (Optional[float], optional):
+                Passed to `sklearn.model_selection.train_test_split`. Defaults to `None`.
+            random_state (Union[int, np.random.RandomState, None], optional):
+                Passed to `sklearn.model_selection.train_test_split`. Defaults to `None`.
+            shuffle (bool, optional):
+                Passed to `sklearn.model_selection.train_test_split`. Defaults to `True`.
+            stratify (Any, optional):
+                Passed to `sklearn.model_selection.train_test_split`. Defaults to `None`.
+
         Returns:
             Tuple[Self, Self]: The split tuple ``(dataset_train, dataset_test)``.
         """
@@ -238,10 +300,10 @@ def split(
 
         Args:
             splitter (Splitter): A `sklearn` splitter.
-            kwargs (Any): Additional keyword arguments to be passed to the ``splitter``'s ``split`` method.
+            **kwargs (Any): Additional keyword arguments to be passed to the ``splitter``'s ``split`` method.
 
         Yields:
-            Generator[Tuple[Self, Self], None, None]: ``(dataset_train, dataset_test)`` for each split.
+            Tuple[Self, Self]: ``(dataset_train, dataset_test)`` for each split.
         """
         sample_ilocs: Any = list(range(len(self)))
         for sample_ilocs_train, sample_ilocs_test in splitter.split(X=sample_ilocs, **kwargs):
@@ -284,6 +346,11 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return True
 
 
@@ -352,10 +419,20 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return self.predictive.targets is not None
 
     @property
     def predict_ready(self) -> bool:
+        """Check if the dataset is ready to be predicted on.
+
+        Returns:
+            bool: Whether the dataset is ready to be predicted on.
+        """
         return True
 
 
@@ -400,10 +477,20 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return self.predictive.targets is not None
 
     @property
     def predict_ready(self) -> bool:
+        """Check if the dataset is ready to be predicted on.
+
+        Returns:
+            bool: Whether the dataset is ready to be predicted on.
+        """
         return True
 
 
@@ -448,10 +535,20 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return self.predictive.targets is not None
 
     @property
     def predict_ready(self) -> bool:
+        """Check if the dataset is ready to be predicted on.
+
+        Returns:
+            bool: Whether the dataset is ready to be predicted on.
+        """
         return True
 
 
@@ -505,10 +602,20 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return self.predictive.targets is not None and self.predictive.treatments is not None
 
     @property
     def predict_ready(self) -> bool:
+        """Check if the dataset is ready to be predicted on.
+
+        Returns:
+            bool: Whether the dataset is ready to be predicted on.
+        """
         return self.predictive.treatments is not None
 
 
@@ -562,8 +669,18 @@ def _validate(self) -> None:
 
     @property
     def fit_ready(self) -> bool:
+        """Check if the dataset is ready to be fit on.
+
+        Returns:
+            bool: Whether the dataset is ready to be fit on.
+        """
         return self.predictive.targets is not None and self.predictive.treatments is not None
 
     @property
     def predict_ready(self) -> bool:
+        """Check if the dataset is ready to be predicted on.
+
+        Returns:
+            bool: Whether the dataset is ready to be predicted on.
+        """
         return self.predictive.treatments is not None
diff --git a/src/tempor/data/pandera_utils.py b/src/tempor/data/pandera_utils.py
index d041c02a..40d92768 100644
--- a/src/tempor/data/pandera_utils.py
+++ b/src/tempor/data/pandera_utils.py
@@ -1,3 +1,5 @@
+"""Utilities for `pandera` validation."""
+
 from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NoReturn, Optional, Tuple, Type, Union, cast
 
 import numpy as np
@@ -85,18 +87,45 @@ def _get_pa_init_args(pa_object: Any, param_names: List[str]) -> Dict[str, Any]:
 
 
 def update_schema(schema: pa.DataFrameSchema, **kwargs: Any) -> pa.DataFrameSchema:
+    """Update a pandera dataframe schema with ``kwargs``.
+
+    Args:
+        schema (pa.DataFrameSchema): `pandera` dataframe schema.
+        **kwargs (Any): keyword arguments to update ``schema`` with.
+
+    Returns:
+        pa.DataFrameSchema: `pandera` dataframe schema.
+    """
     items = _get_pa_init_args(schema, param_names=_PA_DF_SCHEMA_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.DataFrameSchema(**items)
 
 
 def update_index(index: pa.Index, **kwargs: Any) -> pa.Index:
+    """Update a pandera index with ``kwargs``.
+
+    Args:
+        index (pa.Index): `pandera` index.
+        **kwargs (Any): keyword arguments to update ``index`` with.
+
+    Returns:
+        pa.Index: `pandera` index.
+    """
     items = _get_pa_init_args(index, param_names=_PA_INDEX_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.Index(**items)
 
 
 def update_multiindex(multi_index: pa.MultiIndex, **kwargs: Any) -> pa.MultiIndex:
+    """Update a pandera multiindex with ``kwargs``.
+
+    Args:
+        multi_index (pa.MultiIndex): `pandera` multiindex.
+        **kwargs (Any): keyword arguments to update ``multi_index`` with.
+
+    Returns:
+        pa.MultiIndex: `pandera` multiindex.
+    """
     items = _get_pa_init_args(multi_index, param_names=_PA_MULTI_INDEX_INIT_PARAMETERS)
     items.update(kwargs)
     return pa.MultiIndex(**items)
@@ -183,6 +212,11 @@ def __class_getitem__(cls: Type, item: Iterable[Union[data_typing.Dtype, pa.Data
         return pd_engine.Engine.register_dtype(pa_dtypes.immutable(cls_))  # type: ignore
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return self.name
 
     def check(
@@ -231,12 +265,30 @@ def coerce(self, data_container: Any) -> NoReturn:
 
 
 def init_schema(data: pd.DataFrame, **kwargs: Any) -> pa.DataFrameSchema:
+    """Initialize a `pandera.DataFrameSchema` from ``data`` using `pandera.infer_schema`.
+
+    Args:
+        data (pd.DataFrame): Input dataframe.
+        **kwargs (Any): Keyword arguments to update the schema with after initialization.
+
+    Returns:
+        pa.DataFrameSchema: `pandera.DataFrameSchema` initialized from ``data``.
+    """
     schema = cast(pa.DataFrameSchema, pa.infer_schema(data))
     schema = update_schema(schema, **kwargs)
     return schema
 
 
 def add_df_checks(schema: pa.DataFrameSchema, *, checks_list: List[pa.Check]) -> pa.DataFrameSchema:
+    """Update ``schema`` with `pandera` checks specified in ``checks_list``.
+
+    Args:
+        schema (pa.DataFrameSchema): DataFrameSchema to add checks to.
+        checks_list (List[pa.Check]): The list of checks.
+
+    Returns:
+        pa.DataFrameSchema: DataFrameSchema with checks added.
+    """
     schema = update_schema(schema, checks=checks_list)
     return schema
 
@@ -379,6 +431,16 @@ class configurable:
 
         @staticmethod
         def column_index_satisfies_dtype(dtype: Any, *, nullable: bool) -> pa.Check:
+            """Return a `pandera.Check` that checks that the column index satisfies ``dtype``. Optionally, also set
+            the ``nullable`` attribute.
+
+            Args:
+                dtype (Any): The dtype to check against.
+                nullable (bool): The nullable attribute to set.
+
+            Returns:
+                pa.Check: The `pandera.Check` defined.
+            """
             series_name = "Column Index"
             error = str(f"DataFrame {series_name} dtype validation failed, must be of type: {dtype}")
 
diff --git a/src/tempor/data/predictive.py b/src/tempor/data/predictive.py
index 90bc1bbf..69bf56e1 100644
--- a/src/tempor/data/predictive.py
+++ b/src/tempor/data/predictive.py
@@ -1,3 +1,7 @@
+"""Module defining the `PredictiveTaskData` class and its subclasses, which are used to store the data components
+relevant for different predictive tasks (e.g. prediction, time-to-event analysis, treatment effects).
+"""
+
 import abc
 from typing import TYPE_CHECKING, Any, Generator, Optional
 
@@ -18,7 +22,12 @@ class PredictiveTaskData(abc.ABC):
     @property
     @abc.abstractmethod
     def predictive_task(self) -> data_typing.PredictiveTask:  # pragma: no cover
-        ...
+        """Return the predictive task enum value corresponding to the class.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
     def __init__(  # pylint: disable=unused-argument
         self,
@@ -26,7 +35,15 @@ def __init__(  # pylint: disable=unused-argument
         targets: Any,
         treatments: Optional[Any],
         **kwargs: Any,
-    ) -> None:  # pragma: no cover
+    ) -> None:
+        """The predictive task data abstract base class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Any): The targets data.
+            treatments (Optional[Any]): The treatments data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         self.parent_dataset = parent_dataset
         # ^ In order to be able to call parent dataset's `validate` method in the targets/treatments property setters.
 
@@ -34,6 +51,11 @@ def __init__(  # pylint: disable=unused-argument
         self._treatments = treatments
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         if self.targets is not None:
             yield "targets", RichReprStrPassthrough(self.targets.short_repr())
         else:
@@ -42,10 +64,20 @@ def __rich_repr__(self) -> Generator:
             yield "treatments", RichReprStrPassthrough(self.treatments.short_repr())
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return rich.pretty.pretty_repr(self)
 
     @property
     def targets(self) -> Optional[samples.DataSamples]:
+        """The property containing the targets data.
+
+        Returns:
+            Optional[samples.DataSamples]: The targets data.
+        """
         return self._targets
 
     @targets.setter
@@ -55,6 +87,11 @@ def targets(self, value: Optional[samples.DataSamples]) -> None:
 
     @property
     def treatments(self) -> Optional[samples.DataSamples]:
+        """The property containing the treatments data.
+
+        Returns:
+            Optional[samples.DataSamples]: The treatments data.
+        """
         return self._treatments
 
     @treatments.setter
@@ -76,11 +113,23 @@ class OneOffPredictionTaskData(PredictiveTaskData):
 
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """Return the predictive task enum value corresponding to the class. Here, ``ONE_OFF_PREDICTION``.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value. Here, ``ONE_OFF_PREDICTION``.
+        """
         return data_typing.PredictiveTask.ONE_OFF_PREDICTION
 
     def __init__(
         self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
+        """The one-off prediction task data class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Optional[data_typing.DataContainer]): The targets data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.StaticSamples(targets) if targets is not None else None
         self._treatments = None
@@ -94,11 +143,23 @@ class TemporalPredictionTaskData(PredictiveTaskData):
 
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """Return the predictive task enum value corresponding to the class. Here, ``TEMPORAL_PREDICTION``.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value. Here, ``TEMPORAL_PREDICTION``.
+        """
         return data_typing.PredictiveTask.TEMPORAL_PREDICTION
 
     def __init__(
         self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
+        """The temporal prediction task data class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Optional[data_typing.DataContainer]): The targets data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
         self._treatments = None
@@ -115,11 +176,23 @@ class TimeToEventAnalysisTaskData(PredictiveTaskData):
 
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """Return the predictive task enum value corresponding to the class. Here, ``TIME_TO_EVENT_ANALYSIS``.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value. Here, ``TIME_TO_EVENT_ANALYSIS``.
+        """
         return data_typing.PredictiveTask.TIME_TO_EVENT_ANALYSIS
 
     def __init__(
         self, parent_dataset: "PredictiveDataset", targets: Optional[data_typing.DataContainer], **kwargs: Any
     ) -> None:
+        """The time-to-event analysis task data class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Optional[data_typing.DataContainer]): The targets data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=None)
         self._targets = samples.EventSamples(targets) if targets is not None else None
         self._treatments = None
@@ -136,6 +209,11 @@ class OneOffTreatmentEffectsTaskData(PredictiveTaskData):
 
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """Return the predictive task enum value corresponding to the class. Here, ``ONE_OFF_TREATMENT_EFFECTS``.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value. Here, ``ONE_OFF_TREATMENT_EFFECTS``.
+        """
         return data_typing.PredictiveTask.ONE_OFF_TREATMENT_EFFECTS
 
     def __init__(
@@ -145,6 +223,14 @@ def __init__(
         treatments: data_typing.DataContainer,
         **kwargs: Any,
     ) -> None:
+        """The one-off treatment effects task data class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Optional[data_typing.DataContainer]): The targets data.
+            treatments (data_typing.DataContainer): The treatments data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=treatments)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
         self._treatments = samples.EventSamples(treatments)
@@ -158,6 +244,11 @@ class TemporalTreatmentEffectsTaskData(PredictiveTaskData):
 
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """Return the predictive task enum value corresponding to the class. Here, ``TEMPORAL_TREATMENT_EFFECTS``.
+
+        Returns:
+            data_typing.PredictiveTask: The predictive task enum value. Here, ``TEMPORAL_TREATMENT_EFFECTS``.
+        """
         return data_typing.PredictiveTask.TEMPORAL_TREATMENT_EFFECTS
 
     def __init__(
@@ -167,6 +258,14 @@ def __init__(
         treatments: data_typing.DataContainer,
         **kwargs: Any,
     ) -> None:
+        """The temporal treatment effects task data class.
+
+        Args:
+            parent_dataset (PredictiveDataset): The parent predictive dataset object.
+            targets (Optional[data_typing.DataContainer]): The targets data.
+            treatments (data_typing.DataContainer): The treatments data.
+            **kwargs (Any): Additional keyword arguments. Currently unused.
+        """
         super().__init__(parent_dataset=parent_dataset, targets=targets, treatments=treatments)
         self._targets = samples.TimeSeriesSamples(targets) if targets is not None else None
         self._treatments = samples.TimeSeriesSamples(treatments)
diff --git a/src/tempor/data/samples.py b/src/tempor/data/samples.py
index ad988047..da7662cb 100644
--- a/src/tempor/data/samples.py
+++ b/src/tempor/data/samples.py
@@ -27,6 +27,11 @@ class DataSamples(abc.ABC):
     @property
     @abc.abstractmethod
     def modality(self) -> data_typing.DataModality:  # pragma: no cover
+        """Return the data modality enum corresponding to the class
+
+        Returns:
+            data_typing.DataModality: The data modality enum.
+        """
         ...
 
     def __init__(
@@ -34,15 +39,31 @@ def __init__(
         data: data_typing.DataContainer,  # pylint: disable=unused-argument
         **kwargs: Any,
     ) -> None:  # pragma: no cover
+        """The abstract base class for all data samples classes.
+
+        Args:
+            data (data_typing.DataContainer): The data container.
+            **kwargs (Any): Any additional keyword arguments.
+        """
         if "_skip_validate" not in kwargs:
             # For efficiency, pass `_skip_validate` internally (e.g. in `__getitem__`)
             # when there is no need to validate.
             self.validate()
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return f"{self.__class__.__name__} with data:\n{self.dataframe()}"
 
     def _repr_html_(self) -> str:
+        """Return a HTML representation of the object, used in Jupyter notebooks.
+
+        Returns:
+            str: The HTML representation of the object.
+        """
         repr_ = (
             # pylint: disable-next=protected-access
             f'<p><span style="font-family: monospace;">{self.__class__.__name__}</span> with data:</p>'
@@ -51,6 +72,11 @@ def _repr_html_(self) -> str:
         return repr_
 
     def validate(self) -> None:
+        """Validate the data contained.
+
+        Raises:
+            tempor.exc.DataValidationException: Raised if data validation fails.
+        """
         with log_helpers.exc_to_log():
             try:
                 self._validate()
@@ -86,12 +112,14 @@ def from_numpy(
         Args:
             array (np.ndarray):
                 The array that represents the data.
-            sample_index (List[<sample element>], optional):
+            sample_index (Optional[data_typing.SampleIndex], optional):
                 List with sample (row) index for each sample. Optional, if `None`, will be of form ``[0, 1, ...]``.
                 Defaults to `None`.
-            feature_index (List[<feature element>], optional):
+            feature_index (Optional[data_typing.FeatureIndex], optional):
                 List with feature (column) index for each feature. Optional, if `None`, will be of form
                 ``["feat_0", "feat_1", ...]``. Defaults to `None`.
+            **kwargs (Any):
+                Any additional keyword arguments.
 
         Returns:
             DataSamples: :class:`DataSamples` object from ``array``.
@@ -126,6 +154,11 @@ def sample_index(self) -> data_typing.SampleIndex:  # pragma: no cover
         ...
 
     def __len__(self) -> int:
+        """The length, which is the number of samples.
+
+        Returns:
+            int: The number of samples.
+        """
         return self.num_samples
 
     @property
@@ -136,10 +169,23 @@ def num_features(self) -> int:  # pragma: no cover
 
     @abc.abstractmethod
     def short_repr(self) -> str:  # pragma: no cover
+        """A short string representation of the object.
+
+        Returns:
+            str: The short string representation of the object.
+        """
         ...
 
     @abc.abstractmethod
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:  # pragma: no cover
+        """Return a new subset :class:`DataSamples` object with the data indexed by the ``key``.
+
+        Args:
+            key (data_typing.GetItemKey): The key to index the data by.
+
+        Returns:
+            Self: A new subset :class:`DataSamples` object.
+        """
         ...
 
 
@@ -174,14 +220,16 @@ def __init__(
         """Create a :class:`StaticSamples` object from the ``data``.
 
         Args:
-            data (numpy.ndarray | pandas.DataFrame):
+            data (data_typing.DataContainer):
                 A container with the data.
-            sample_index (List[<sample element>], optional):
+            sample_index (Optional[data_typing.SampleIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`. List with sample (row) index for each sample. Optional,
                 if `None`, will be of form ``[0, 1, ...]``. Defaults to `None`.
-            feature_index (List[<feature element>], optional):
+            feature_index (Optional[data_typing.FeatureIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`.  List with feature (column) index for each feature.
                 Optional, if `None`, will be of form ``["feat_0", "feat_1", ...]``. Defaults to `None`.
+            **kwargs (Any):
+                Any additional keyword arguments to pass to the constructor.
         """
         if isinstance(data, pd.DataFrame):
             self._data = data
@@ -193,6 +241,11 @@ def __init__(
 
     @property
     def modality(self) -> data_typing.DataModality:
+        """Return the data modality enum corresponding to the class. Here, ``STATIC``.
+
+        Returns:
+            data_typing.DataModality: The data modality enum. Here, ``STATIC``.
+        """
         return data_typing.DataModality.STATIC
 
     def _validate(self) -> None:
@@ -241,6 +294,16 @@ def _validate(self) -> None:
 
     @staticmethod
     def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "StaticSamples":
+        """Create :class:`StaticSamples` from `pandas.DataFrame`. The rows represent samples, the columns represent
+        features.
+
+        Args:
+            dataframe (pd.DataFrame): The dataframe that represents the data.
+            **kwargs (Any): Any additional keyword arguments to pass to the constructor.
+
+        Returns:
+            StaticSamples: :class:`StaticSamples` object from ``dataframe``.
+        """
         return StaticSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -251,6 +314,18 @@ def from_numpy(
         feature_index: Optional[data_typing.FeatureIndex] = None,
         **kwargs: Any,
     ) -> "StaticSamples":
+        """Create :class:`StaticSamples` from `numpy.ndarray`. The 0th dimension represents samples, the 1st dimension
+        represents features.
+
+        Args:
+            array (np.ndarray): The array with the data.
+            sample_index (Optional[data_typing.SampleIndex], optional): Sample indices to assign. Defaults to None.
+            feature_index (Optional[data_typing.FeatureIndex], optional): Feature indices to assign. Defaults to None.
+            **kwargs (Any): Any additional keyword arguments to pass to the constructor.
+
+        Returns:
+            StaticSamples: :class:`StaticSamples` object created from the ``array``.
+        """
         return StaticSamples(array, sample_index=sample_index, feature_index=feature_index, **kwargs)
 
     @staticmethod
@@ -268,26 +343,70 @@ def _array_to_df(
         return pd.DataFrame(data=array, index=sample_index, columns=feature_index, **kwargs)
 
     def numpy(self, **kwargs: Any) -> np.ndarray:
+        """Return the data as a `numpy.ndarray`.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments. Currently unused.
+
+        Returns:
+            np.ndarray: The `numpy.ndarray`.
+        """
         return self._data.to_numpy()
 
     def dataframe(self, **kwargs: Any) -> pd.DataFrame:
+        """Return the data as a `pandas.DataFrame`.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments. Currently unused.
+
+        Returns:
+            pd.DataFrame: _description_
+        """
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
+        """Return a list representing sample indexes.
+
+        Returns:
+            data_typing.SampleIndex: Sample indexes.
+        """
         return list(self._data.index)  # pyright: ignore
 
     @property
     def num_samples(self) -> int:
+        """Return number of samples.
+
+        Returns:
+            int: Number of samples.
+        """
         return self._data.shape[0]
 
     @property
     def num_features(self) -> int:
+        """Return number of features.
+
+        Returns:
+            int: Number of features.
+        """
         return self._data.shape[1]
 
     def short_repr(self) -> str:
+        """A short string representation of the object.
+
+        Returns:
+            str: The short representation.
+        """
         return f"{self.__class__.__name__}([{self.num_samples}, {self.num_features}])"
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
+        """Return a new subset :class:`StaticSamples` object with the data indexed by the ``key``.
+
+        Args:
+            key (data_typing.GetItemKey): The key to index the data by.
+
+        Returns:
+            Self: A new subset :class:`StaticSamples` object.
+        """
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
         return StaticSamples(  # type: ignore [return-value]
             self._data.iloc[key_, :],  # pyright: ignore
@@ -336,6 +455,11 @@ class TimeSeriesSamples(DataSamples):
 
     @property
     def modality(self) -> data_typing.DataModality:
+        """Return the data modality enum corresponding to the class. Here, ``TIME_SERIES``.
+
+        Returns:
+            data_typing.DataModality: The data modality enum. Here, ``TIME_SERIES``.
+        """
         return data_typing.DataModality.TIME_SERIES
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
@@ -359,21 +483,24 @@ def __init__(
         be the same across the feature dimension (dim 2) for each sample.
 
         Args:
-            data (numpy.ndarray | pandas.DataFrame):
+            data (data_typing.DataContainer):
                 A container with the data.
             padding_indicator (Any, optional):
                 Padding indicator used in ``data`` to indicate padding. Defaults to `None`.
-            sample_index (List[<sample element>], optional):
+            sample_index (Optional[data_typing.SampleIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`. List with sample (row) index for each sample.
                 Optional, if `None`, will be of form ``[0, 1, ...]``. Defaults to `None`.
-            time_indexes (List[List[<timestep element>]], optional):
+            time_indexes (Optional[data_typing.TimeIndexList], optional):
                 Used only if ``data`` is a `numpy.ndarray`. List of lists containing timesteps for each sample (outer
                 list should be the same length as dim 0 of `data`, inner list should contain as many elements as each
                 sample has timesteps). Optional, if `None`, will be of form ``[[0, 1, ...], [0, 1, ...], ...]``
                 Defaults to `None`.
-            feature_index (List[<feature element>], optional):
+            feature_index (Optional[data_typing.FeatureIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`.  List with feature (column) index for each feature.
                 Optional, if `None`, will be of form ``["feat_0", "feat_1", ...]``.
+                Defaults to `None`.
+            **kwargs (Any):
+                Any additional keyword arguments to pass to the constructor.
         """
         if isinstance(data, pd.DataFrame):
             self._data = data
@@ -448,6 +575,16 @@ def _validate(self) -> None:
 
     @staticmethod
     def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "TimeSeriesSamples":
+        """Create :class:`TimeSeriesSamples` from `pandas.DataFrame`. This row index of the dataframe should be a
+        2-level multiindex (sample, timestep). The columns should be the features.
+
+        Args:
+            dataframe (pd.DataFrame): The dataframe that contains the data.
+            **kwargs (Any): Any additional keyword arguments to pass to the constructor.
+
+        Returns:
+            TimeSeriesSamples: The :class:`TimeSeriesSamples` object created from the ``dataframe``.
+        """
         return TimeSeriesSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -460,6 +597,31 @@ def from_numpy(
         feature_index: Optional[data_typing.FeatureIndex] = None,
         **kwargs: Any,
     ) -> "TimeSeriesSamples":
+        """Create :class:`TimeSeriesSamples` from `numpy.ndarray`.
+
+        This should be a 3D array, with dimensions ``(sample, timestep, feature)``.
+
+        Optionally, padding values of ``padding_indicator`` can be set inside the array to pad out the length of arrays
+        of different samples in case they differ. Padding needs to go at the end of the timesteps (dim 1). Padding must
+        be the same across the feature dimension (dim 2) for each sample.
+
+        Args:
+            array (np.ndarray):
+                The array that contains the data.
+            padding_indicator (Any, optional):
+                The padding indicator value. Defaults to `None`.
+            sample_index (Optional[data_typing.SampleIndex], optional):
+                Sample indexes as a list. Defaults to `None`.
+            time_indexes (Optional[data_typing.TimeIndexList], optional):
+                Time indexes as a list of list (that is, time indexes per sample). Defaults to `None`.
+            feature_index (Optional[data_typing.FeatureIndex], optional):
+                Feature indexes as a list. Defaults to `None`.
+            **kwargs (Any):
+                Any additional keyword arguments.
+
+        Returns:
+            TimeSeriesSamples: The :class:`TimeSeriesSamples` object created from the ``array``.
+        """
         return TimeSeriesSamples(
             array,
             padding_indicator=padding_indicator,
@@ -496,14 +658,38 @@ def _array_to_df(  # pylint: disable=unused-argument
         )
 
     def numpy(self, *, padding_indicator: Any = DATA_SETTINGS.default_padding_indicator, **kwargs: Any) -> np.ndarray:
+        """Return the data as a `numpy.ndarray`.
+
+        Args:
+            padding_indicator (Any, optional):
+                Padding indicator value. Defaults to `DATA_SETTINGS.default_padding_indicator`.
+            **kwargs (Any):
+                Any additional keyword arguments. Currently unused.
+
+        Returns:
+            np.ndarray: The `numpy.ndarray`.
+        """
         return utils.multiindex_timeseries_dataframe_to_array3d(
             df=self._data, padding_indicator=padding_indicator, max_timesteps=None
         )
 
     def dataframe(self, **kwargs: Any) -> pd.DataFrame:
+        """Return the data as a `pandas.DataFrame`.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments. Currently unused.
+
+        Returns:
+            pd.DataFrame: The `pandas.DataFrame`.
+        """
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
+        """Get a list containing sample indexes.
+
+        Returns:
+            data_typing.SampleIndex: A list containing sample indexes.
+        """
         return list(utils.get_df_index_level0_unique(self._data))  # pyright: ignore
 
     def time_indexes(self) -> data_typing.TimeIndexList:
@@ -511,7 +697,7 @@ def time_indexes(self) -> data_typing.TimeIndexList:
         elements.
 
         Returns:
-            List[List[<timestep element>]]: A list containing time indexes for each sample.
+            data_typing.TimeIndexList: A list containing time indexes for each sample.
         """
         return list(self.time_indexes_as_dict().values())  # pyright: ignore
 
@@ -520,7 +706,7 @@ def time_indexes_as_dict(self) -> data_typing.SampleToTimeIndexDict:
         step elements.
 
         Returns:
-            Dict[<sample element>, List[<timestep element>]]: A list containing time indexes for each sample.
+            data_typing.SampleToTimeIndexDict: The dictionary mapping each sample index to its time index.
         """
         multiindex = self._data.index
         if TYPE_CHECKING:  # pragma: no cover
@@ -554,7 +740,7 @@ def num_timesteps_as_dict(self) -> data_typing.SampleToNumTimestepsDict:
         """Get a dictionary mapping each sample index to its the number of timesteps.
 
         Returns:
-            List[int]: List containing the number of timesteps for each sample.
+            data_typing.SampleToNumTimestepsDict: List containing the number of timesteps for each sample.
         """
         return {key: len(x) for key, x in self.time_indexes_as_dict().items()}  # type: ignore
 
@@ -577,17 +763,40 @@ def list_of_dataframes(self) -> List[pd.DataFrame]:
 
     @property
     def num_samples(self) -> int:
+        """Return number of samples.
+
+        Returns:
+            int: Number of samples.
+        """
         sample_ids = utils.get_df_index_level0_unique(self._data)
         return len(sample_ids)
 
     @property
     def num_features(self) -> int:
+        """Return number of features.
+
+        Returns:
+            int: Number of features.
+        """
         return self._data.shape[1]
 
     def short_repr(self) -> str:
+        """A short string representation of the object.
+
+        Returns:
+            str: The short representation.
+        """
         return f"{self.__class__.__name__}([{self.num_samples}, *, {self.num_features}])"
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
+        """Return a subset :class:`TimeSeriesSamples` object with the data indexed by the ``key``.
+
+        Args:
+            key (data_typing.GetItemKey): The key to index the data by.
+
+        Returns:
+            Self: A new subset :class:`TimeSeriesSamples` object.
+        """
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
         sample_index = utils.get_df_index_level0_unique(self._data)
         selected = list(sample_index[key_])  # pyright: ignore
@@ -607,6 +816,11 @@ class EventSamples(DataSamples):
 
     @property
     def modality(self) -> data_typing.DataModality:
+        """Return the data modality enum corresponding to the class. Here, ``EVENT``.
+
+        Returns:
+            data_typing.DataModality: The data modality enum. Here, ``EVENT``.
+        """
         return data_typing.DataModality.EVENT
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
@@ -621,14 +835,16 @@ def __init__(
         """Create an :class:`EventSamples` object from the ``data``.
 
         Args:
-            data (numpy.ndarray | pandas.DataFrame):
+            data (data_typing.DataContainer):
                 A container with the data.
-            sample_index (List[<sample element>], optional):
+            sample_index (Optional[data_typing.SampleIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`. List with sample (row) index for each sample. Optional,
                 if `None`, will be of form ``[0, 1, ...]``. Defaults to `None`.
-            feature_index (List[<feature element>], optional):
+            feature_index (Optional[data_typing.FeatureIndex], optional):
                 Used only if ``data`` is a `numpy.ndarray`.  List with feature (column) index for each feature.
                 Optional, if `None`, will be of form ``["feat_0", "feat_1", ...]``. Defaults to `None`.
+            **kwargs (Any):
+                Any additional keyword arguments to pass to the constructor.
         """
         if isinstance(data, pd.DataFrame):
             self._data = data
@@ -704,6 +920,17 @@ def _validate(self) -> None:
 
     @staticmethod
     def from_dataframe(dataframe: pd.DataFrame, **kwargs: Any) -> "EventSamples":
+        """Create :class:`EventSamples` from `pandas.DataFrame`. The row index of the dataframe should be the sample
+        indexes. The columns should be the features. Each feature should contain a tuple of ``(time, value)``
+        representing the event.
+
+        Args:
+            dataframe (pd.DataFrame): The dataframe that contains the data.
+            **kwargs (Any): Any additional keyword arguments to pass to the constructor.
+
+        Returns:
+            EventSamples: The :class:`EventSamples` object created from the ``dataframe``.
+        """
         return EventSamples(dataframe, **kwargs)
 
     @staticmethod
@@ -714,6 +941,18 @@ def from_numpy(
         feature_index: Optional[data_typing.FeatureIndex] = None,
         **kwargs: Any,
     ) -> "EventSamples":
+        """Create :class:`EventSamples` from `numpy.ndarray`. The array should be a 2D array, with dimensions
+        ``(sample, feature)``. Each element should contain a tuple of ``(time, value)`` representing the event.
+
+        Args:
+            array (np.ndarray): The array that contains the data.
+            sample_index (Optional[data_typing.SampleIndex], optional): Sample indexes. Defaults to `None`.
+            feature_index (Optional[data_typing.FeatureIndex], optional): Feature index. Defaults to `None`.
+            **kwargs (Any): Any additional keyword arguments to pass to the constructor.
+
+        Returns:
+            EventSamples: The :class:`EventSamples` object created from the ``array``.
+        """
         return EventSamples(array, sample_index=sample_index, feature_index=feature_index, **kwargs)
 
     @staticmethod
@@ -731,21 +970,52 @@ def _array_to_df(
         return pd.DataFrame(data=array, index=sample_index, columns=feature_index, **kwargs)
 
     def numpy(self, **kwargs: Any) -> np.ndarray:
+        """Return the data as a `numpy.ndarray`.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments. Currently unused.
+
+        Returns:
+            np.ndarray: The `numpy.ndarray`.
+        """
         # TODO: May want at option to return a scikit-survive -style array.
         return self._data.to_numpy()
 
     def dataframe(self, **kwargs: Any) -> pd.DataFrame:
+        """Return the data as a `pandas.DataFrame`.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments. Currently unused.
+
+        Returns:
+            pd.DataFrame: The `pandas.DataFrame`.
+        """
         return self._data
 
     def sample_index(self) -> data_typing.SampleIndex:
+        """Return a list representing sample indexes.
+
+        Returns:
+            data_typing.SampleIndex: Sample indexes.
+        """
         return list(self._data.index)  # pyright: ignore
 
     @property
     def num_samples(self) -> int:
+        """Return number of samples.
+
+        Returns:
+            int: Number of samples.
+        """
         return self._data.shape[0]
 
     @property
     def num_features(self) -> int:
+        """Return number of features.
+
+        Returns:
+            int: Number of features.
+        """
         return self._data.shape[1]
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
@@ -794,9 +1064,22 @@ def split_as_two_dataframes(
         return df_event_times, df_event_values
 
     def short_repr(self) -> str:
+        """A short string representation of the object.
+
+        Returns:
+            str: The short representation.
+        """
         return f"{self.__class__.__name__}([{self.num_samples}, {self.num_features}])"
 
     def __getitem__(self, key: data_typing.GetItemKey) -> Self:
+        """Return a new subset :class:`EventSamples` object with the data indexed by the ``key``.
+
+        Args:
+            key (data_typing.GetItemKey): The key to index the data by.
+
+        Returns:
+            Self: A new subset :class:`EventSamples` object.
+        """
         key_ = utils.ensure_pd_iloc_key_returns_df(key)
         return EventSamples(  # type: ignore [return-value]
             self._data.iloc[key_, :],  # pyright: ignore
diff --git a/src/tempor/data/utils.py b/src/tempor/data/utils.py
index 54fa8140..11f18840 100644
--- a/src/tempor/data/utils.py
+++ b/src/tempor/data/utils.py
@@ -1,3 +1,5 @@
+"""Module containing utility functions for data format management."""
+
 import dataclasses
 import itertools
 from typing import Any, ClassVar, Iterable, List, Optional, Sequence, Tuple, Union
@@ -32,6 +34,16 @@ def value_in_df(df: pd.DataFrame, *, value: Any) -> bool:
 
 
 def set_df_column_names_inplace(df: pd.DataFrame, names: Sequence) -> pd.DataFrame:
+    """Set column names of ``df`` to ``names`` inplace. Used to handle different behaviour of ``set_axis`` in different
+    pandas versions.
+
+    Args:
+        df (pd.DataFrame): Dataframe.
+        names (Sequence): Columns names.
+
+    Returns:
+        pd.DataFrame: Dataframe with column names set.
+    """
     if Version(pd.__version__) < Version("1.5"):  # pragma: no cover
         df.set_axis(names, axis="columns", inplace=True)  # pyright: ignore
         return df
@@ -40,6 +52,14 @@ def set_df_column_names_inplace(df: pd.DataFrame, names: Sequence) -> pd.DataFra
 
 
 def get_df_index_level0_unique(df: pd.DataFrame) -> pd.Index:
+    """Return the unique values of the level 0 index of ``df``.
+
+    Args:
+        df (pd.DataFrame): The dataframe.
+
+    Returns:
+        pd.Index: The unique values of the level 0 index of ``df``.
+    """
     return df.index.get_level_values(level=0).unique()
 
 
@@ -59,7 +79,7 @@ def multiindex_timeseries_dataframe_to_array3d(
         padding_indicator (Any):
             padding indicator value to use to pad the output array in case of unequal number of timesteps for
             different samples.
-        max_timesteps (int, optional):
+        max_timesteps (Optional[int], optional):
             Maximum number of timesteps to use. This will become the size of the dim 1 of the output array. If set to
             `None`, this dimension will be set as the highest number of timesteps among the samples. Defaults to `None`.
 
@@ -104,6 +124,14 @@ def check_bool_array1d_trues_consecutive(array: np.ndarray, at_beginning: bool =
         False
         >>> check_bool_array1d_trues_consecutive(np.asarray([False, True, True, True]), at_end=True)
         True
+
+    Args:
+        array (np.ndarray): Input array.
+        at_beginning (bool, optional): Check if first element is `True`. Defaults to `False`.
+        at_end (bool, optional): Check if last element is `True`. Defaults to `False`.
+
+    Returns:
+        bool: The result of the check.
     """
     if array.ndim != 1:
         raise ValueError(EXCEPTION_MESSAGES.expected_array1d)
@@ -136,6 +164,12 @@ def check_bool_array2d_identical_along_dim1(array: np.ndarray) -> bool:
         True
         >>> check_bool_array2d_identical_along_dim1(np.asarray([[True, True, False], [False, True, False]]).T)
         False
+
+    Args:
+        array (np.ndarray): Input array.
+
+    Returns:
+        bool: The result of the check.
     """
     if array.ndim != 2:
         raise ValueError(EXCEPTION_MESSAGES.expected_array2d)
@@ -157,6 +191,13 @@ def get_array1d_length_until_padding(array: np.ndarray, padding_indicator: Any =
         4
         >>> get_array1d_length_until_padding(np.asarray([1, 8, -3, 9, 5]), padding_indicator=pad)
         5
+
+    Args:
+        array (np.ndarray): Input array.
+        padding_indicator (Any, optional): Padding indicator. Defaults to None.
+
+    Returns:
+        int: Length of ``array`` up to first padding.
     """
     if array.ndim != 1:
         raise ValueError(EXCEPTION_MESSAGES.expected_array1d)
@@ -210,6 +251,15 @@ def get_seq_lengths_timeseries_array3d(array: np.ndarray, padding_indicator: Any
         >>> array = np.transpose(array, (0, 2, 1))
         >>> get_seq_lengths_timeseries_array3d(array, padding_indicator=pad)
         [4, 2]
+
+    Args:
+        array (np.ndarray):
+            3D numpy `array` that represents timeseries like ``(sample, timestep, feature)``.
+        padding_indicator (Any, optional):
+            Padding indicator used in `array` to indicate padding. Defaults to `None`.
+
+    Returns:
+        List[int]: List of lengths (number of [non-padding] timesteps) for each sample.
     """
     validate_timeseries_array3d(array, padding_indicator)
     is_padded = padding_indicator is not None
@@ -262,6 +312,13 @@ def make_sample_time_index_tuples(
         >>> time_indexes = [[1, 2, 3], [1, 5, 9, 10]]
         >>> make_sample_time_index_tuples(sample_index, time_indexes)
         [('s1', 1), ('s1', 2), ('s1', 3), ('s2', 1), ('s2', 5), ('s2', 9), ('s2', 10)]
+
+    Args:
+        sample_index (data_typing.SampleIndex): List of sample IDs.
+        time_indexes (data_typing.TimeIndexList): List of lists of timesteps for each sample.
+
+    Returns:
+        data_typing.SampleTimeIndexTuples: List of tuples like ``[(<sample ID>, <timestep>), ...]``.
     """
     if len(sample_index) != len(time_indexes):
         raise ValueError("Expected the same number of elements in `sample_index` and `time_indexes`")
@@ -294,15 +351,15 @@ def array3d_to_multiindex_timeseries_dataframe(
     Args:
         array (np.ndarray):
             3D numpy `array` that represents timeseries like ``(sample, timestep, feature)``.
-        sample_index (List[<sample element>):
+        sample_index (data_typing.SampleIndex):
             List of sample IDs (should be the same length as dim 0 of `array`).
-        time_indexes (List[List[<timestep element>]]):
+        time_indexes (data_typing.TimeIndexList):
             List of lists containing timesteps for each sample (outer list should be the same length as dim 0 of
             `array`, inner list should contain as many elements as each sample has timesteps).
-        feature_index (List[<feature element>]):
+        feature_index (data_typing.FeatureIndex):
             List of feature names.
         padding_indicator (Any, optional):
-            Padding indicator used in `array` to indicate padding. Defaults to None.
+            Padding indicator used in `array` to indicate padding. Defaults to `None`.
 
     Returns:
         pd.DataFrame: Resultant dataframe.
@@ -328,6 +385,18 @@ def list_of_dataframes_to_multiindex_timeseries_dataframe(
     time_indexes: Optional[data_typing.TimeIndexList] = None,
     feature_index: Optional[data_typing.FeatureIndex] = None,
 ) -> pd.DataFrame:
+    """Given a list of dataframes ``list_of_dataframes``, ``sample_index``, [``time_indexes``, ``feature_index``,]
+    build a 2-level multiindex (sample, timestep) `pandas.DataFrame`.
+
+    Args:
+        list_of_dataframes (List[pd.DataFrame]): List of dataframes.
+        sample_index (data_typing.SampleIndex): List of sample IDs.
+        time_indexes (Optional[data_typing.TimeIndexList], optional): List of lists of time indexes. Defaults to `None`.
+        feature_index (Optional[data_typing.FeatureIndex], optional): Feature index. Defaults to `None`.
+
+    Returns:
+        pd.DataFrame: Resultant dataframe.
+    """
     dfs: List[pd.DataFrame] = list_of_dataframes
 
     if time_indexes is not None and len(list_of_dataframes) != len(time_indexes):
@@ -451,6 +520,14 @@ def datetime_time_index_to_float(time_index: Union[data_typing.TimeIndex, pd.Ind
 
 
 def ensure_pd_iloc_key_returns_df(key: Any) -> Union[Iterable, slice]:
+    """Modify ``key`` such that when this is passed to ``pd.DataFrame.iloc``, the result is always a dataframe.
+
+    Args:
+        key (Any): Input key.
+
+    Returns:
+        Union[Iterable, slice]: Modified key.
+    """
     if isinstance(key, slice):
         key_: Any = key
     elif tempor.core.utils.is_iterable(key):
diff --git a/src/tempor/datasources/__init__.py b/src/tempor/datasources/__init__.py
index 2064682f..f017114e 100644
--- a/src/tempor/datasources/__init__.py
+++ b/src/tempor/datasources/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory for data sources."""
+
 from . import prediction, time_to_event, treatments
 
 __all__ = [
diff --git a/src/tempor/datasources/datasource.py b/src/tempor/datasources/datasource.py
index 11c80232..048e134f 100644
--- a/src/tempor/datasources/datasource.py
+++ b/src/tempor/datasources/datasource.py
@@ -1,3 +1,5 @@
+"""Module defining the data source classes."""
+
 # pylint: disable=unnecessary-ellipsis
 
 import abc
@@ -30,7 +32,7 @@ def __init__(self, **kwargs: Any) -> None:  # pylint: disable=unused-argument
         """Initializer for `DataSource`.
 
         Args:
-            kwargs: Any additional keyword arguments for the :class:`DataSource`.
+            **kwargs (Any): Any additional keyword arguments for the :class:`DataSource`.
         """
         plugins.Plugin.__init__(self)
 
@@ -78,20 +80,22 @@ def requires_internet(cls: "Type[DataSource]") -> bool:
     @property
     @abc.abstractmethod
     def predictive_task(self) -> data_typing.PredictiveTask:  # pragma: no cover
-        """The expected predictive task of the loaded `~tempor.data.dataset.DataSet`.
+        """The expected predictive task of the loaded dataset.
 
         Returns:
-            data_typing.PredictiveTask: Predictive task of loaded `~tempor.data.dataset.DataSet`.
+            data_typing.PredictiveTask: Predictive task of loaded dataset.
         """
         ...
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.PredictiveDataset:  # pragma: no cover
-        """The method that should return the loaded `~tempor.data.dataset.DataSet` for the appropriate
-        ``predictive_task``.
+        """The method that should return the loaded dataset for the appropriate ``predictive_task``.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
 
         Returns:
-            dataset.Dataset: The loaded `~tempor.data.dataset.DataSet`.
+            dataset.PredictiveDataset: The loaded dataset.
         """
         ...
 
@@ -99,48 +103,113 @@ def load(self, **kwargs: Any) -> dataset.PredictiveDataset:  # pragma: no cover
 class OneOffPredictionDataSource(DataSource):
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """The expected predictive task of the loaded dataset. Here, it is ``ONE_OFF_PREDICTION``.
+
+        Returns:
+            data_typing.PredictiveTask: Predictive task of loaded dataset. Here, it is ``ONE_OFF_PREDICTION``.
+        """
         return data_typing.PredictiveTask.ONE_OFF_PREDICTION
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:  # pragma: no cover
+        """The method that should return a one-off prediction dataset.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            dataset.OneOffPredictionDataset: The loaded dataset.
+        """
         ...
 
 
 class TemporalPredictionDataSource(DataSource):
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """The expected predictive task of the loaded dataset. Here, it is ``TEMPORAL_PREDICTION``.
+
+        Returns:
+            data_typing.PredictiveTask: Predictive task of loaded dataset. Here, it is ``TEMPORAL_PREDICTION``.
+        """
         return data_typing.PredictiveTask.TEMPORAL_PREDICTION
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:  # pragma: no cover
+        """The method that should return a temporal prediction dataset.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            dataset.TemporalPredictionDataset: The loaded dataset.
+        """
         ...
 
 
 class TimeToEventAnalysisDataSource(DataSource):
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """The expected predictive task of the loaded dataset. Here, it is ``TIME_TO_EVENT_ANALYSIS``.
+
+        Returns:
+            data_typing.PredictiveTask: Predictive task of loaded dataset. Here, it is ``TIME_TO_EVENT_ANALYSIS``.
+        """
         return data_typing.PredictiveTask.TIME_TO_EVENT_ANALYSIS
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.TimeToEventAnalysisDataset:  # pragma: no cover
+        """The method that should return a time-to-event analysis dataset.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            dataset.TimeToEventAnalysisDataset: The loaded dataset.
+        """
         ...
 
 
 class OneOffTreatmentEffectsDataSource(DataSource):
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """The expected predictive task of the loaded dataset. Here, it is ``ONE_OFF_TREATMENT_EFFECTS``.
+
+        Returns:
+            data_typing.PredictiveTask: Predictive task of loaded dataset. Here, it is ``ONE_OFF_TREATMENT_EFFECTS``.
+        """
         return data_typing.PredictiveTask.ONE_OFF_TREATMENT_EFFECTS
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.OneOffTreatmentEffectsDataset:  # pragma: no cover
+        """The method that should return a one-off treatment effects dataset.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            dataset.OneOffTreatmentEffectsDataset: The loaded dataset.
+        """
         ...
 
 
 class TemporalTreatmentEffectsDataSource(DataSource):
     @property
     def predictive_task(self) -> data_typing.PredictiveTask:
+        """The expected predictive task of the loaded dataset. Here, it is ``TEMPORAL_TREATMENT_EFFECTS``.
+
+        Returns:
+            data_typing.PredictiveTask: Predictive task of loaded dataset. Here, it is ``TEMPORAL_TREATMENT_EFFECTS``.
+        """
         return data_typing.PredictiveTask.TEMPORAL_TREATMENT_EFFECTS
 
     @abc.abstractmethod
     def load(self, **kwargs: Any) -> dataset.TemporalTreatmentEffectsDataset:  # pragma: no cover
+        """The method that should return a temporal treatment effects dataset.
+
+        Args:
+            **kwargs (Any): Any additional keyword arguments.
+
+        Returns:
+            dataset.TemporalTreatmentEffectsDataset: The loaded dataset.
+        """
         ...
diff --git a/src/tempor/datasources/prediction/__init__.py b/src/tempor/datasources/prediction/__init__.py
index eef95d67..ae33865b 100644
--- a/src/tempor/datasources/prediction/__init__.py
+++ b/src/tempor/datasources/prediction/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory for prediction data sources."""
+
 from . import one_off, temporal
 
 __all__ = [
diff --git a/src/tempor/datasources/prediction/one_off/__init__.py b/src/tempor/datasources/prediction/one_off/__init__.py
index 4101def7..54649c3d 100644
--- a/src/tempor/datasources/prediction/one_off/__init__.py
+++ b/src/tempor/datasources/prediction/one_off/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory for one-off prediction data sources."""
+
 from tempor.core import plugins
 from tempor.datasources import datasource
 
diff --git a/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py b/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
index 3893a3e0..661175fc 100644
--- a/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
+++ b/src/tempor/datasources/prediction/one_off/plugin_google_stocks.py
@@ -1,3 +1,5 @@
+"""Module with the Google stocks data source."""
+
 import io
 from pathlib import Path
 from typing import Any
@@ -12,24 +14,34 @@
 from tempor.datasources import datasource
 
 
-# TODO: Docstring to explain the dataset.
 @plugins.register_plugin(name="google_stocks", category="prediction.one_off", plugin_type="datasource")
 class GoogleStocksDataSource(datasource.OneOffPredictionDataSource):
     def __init__(self, seq_len: int = 10, **kwargs: Any) -> None:
+        """Google stocks data source, as found here:
+        https://raw.githubusercontent.com/PacktPublishing/Learning-Pandas-Second-Edition/master/data/goog.csv
+
+        The next day's opening price is the target.
+
+        The samples are the sequence chunks of length ``seq_len``.
+
+        Args:
+            seq_len (int, optional): The number of time steps to use. Defaults to ``10``.
+            **kwargs (Any): Keyword arguments to be passed to the parent class.
+        """
         super().__init__(**kwargs)
 
         self.seq_len = seq_len
         self.df_path = Path(self.dataset_dir()) / "goog.csv"
 
     @staticmethod
-    def url() -> str:
+    def url() -> str:  # noqa: D102
         return "https://raw.githubusercontent.com/PacktPublishing/Learning-Pandas-Second-Edition/master/data/goog.csv"
 
     @staticmethod
-    def dataset_dir() -> str:
+    def dataset_dir() -> str:  # noqa: D102
         return str(Path(GoogleStocksDataSource.data_root_dir) / "google_stocks")
 
-    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:  # noqa: D102
         # Load Google Data
         if not self.df_path.exists():
             s = requests.get(self.url(), timeout=5).content
@@ -41,7 +53,7 @@ def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:
 
         # Flip the data to make chronological data
         df = pd.DataFrame(df.values[::-1], columns=df.columns)
-        T = pd.to_datetime(df["Date"]).astype(np.int64).astype(np.float64) / 10**9
+        T = pd.to_datetime(df["Date"]).astype(np.int64).astype(np.float64) / 10**9  # pyright: ignore
         T = pd.Series(MinMaxScaler().fit_transform(T.values.reshape(-1, 1)).squeeze())  # pyright: ignore
 
         df = df.drop(columns=["Date"])
diff --git a/src/tempor/datasources/prediction/one_off/plugin_sine.py b/src/tempor/datasources/prediction/one_off/plugin_sine.py
index 4922e2b3..e02efd33 100644
--- a/src/tempor/datasources/prediction/one_off/plugin_sine.py
+++ b/src/tempor/datasources/prediction/one_off/plugin_sine.py
@@ -1,3 +1,5 @@
+"""Module with the sine data source."""
+
 from typing import Any
 
 import numpy as np
@@ -24,7 +26,7 @@ def __init__(
         random_state: int = 42,
         **kwargs: Any,
     ) -> None:
-        """Sine data generation.
+        """Sinusoidal data generation. See source code for the specifics.
 
         Args:
             no (int, optional):
@@ -48,6 +50,8 @@ def __init__(
                 The scaling factor to apply to the time series data. Defaults to ``1.0``.
             random_state (int, optional):
                 The random seed to set for `numpy.random.seed`. Defaults to ``42``.
+            **kwargs (Any):
+                Any additional keyword arguments will be passed to parent class constructor.
         """
         super().__init__(**kwargs)
 
@@ -63,14 +67,14 @@ def __init__(
         self.random_state = random_state
 
     @staticmethod
-    def url() -> None:
+    def url() -> None:  # noqa: D102
         return None
 
     @staticmethod
-    def dataset_dir() -> None:
+    def dataset_dir() -> None:  # noqa: D102
         return None
 
-    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffPredictionDataset:  # noqa: D102
         # Initialize the output.
         np.random.seed(self.random_state)
 
diff --git a/src/tempor/datasources/prediction/temporal/__init__.py b/src/tempor/datasources/prediction/temporal/__init__.py
index d40c6e14..31f40df6 100644
--- a/src/tempor/datasources/prediction/temporal/__init__.py
+++ b/src/tempor/datasources/prediction/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Package for temporal prediction data sources."""
+
 from tempor.core import plugins
 from tempor.datasources import datasource
 
diff --git a/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py b/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
index e1ed5b7a..19f347ca 100644
--- a/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
+++ b/src/tempor/datasources/prediction/temporal/plugin_dummy_prediction.py
@@ -1,3 +1,5 @@
+"""Module with the dummy data source for temporal prediction."""
+
 from typing import Any, Optional, cast
 
 from clairvoyance2.datasets.dummy import dummy_dataset
@@ -45,7 +47,7 @@ def __init__(
                 rather than categorical. Defaults to `None`.
             random_state (int, optional):
                 Random state to use. Defaults to ``12345``.
-            kwargs (Any):
+            **kwargs (Any):
                 Any additional keyword arguments will be passed to `~tempor.datasources.DataSource`.
         """
         super().__init__(**kwargs)
@@ -61,14 +63,14 @@ def __init__(
         self.random_state = random_state
 
     @staticmethod
-    def url() -> None:
+    def url() -> None:  # noqa: D102
         return None
 
     @staticmethod
-    def dataset_dir() -> None:
+    def dataset_dir() -> None:  # noqa: D102
         return None
 
-    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:  # noqa: D102
         clv_dataset = dummy_dataset(
             n_samples=self.n_samples,
             temporal_covariates_n_features=self.temporal_covariates_n_features,
diff --git a/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py b/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
index 58226532..26397248 100644
--- a/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
+++ b/src/tempor/datasources/prediction/temporal/plugin_uci_diabetes.py
@@ -1,3 +1,5 @@
+"""Module defining the UCI diabetes data source."""
+
 import time
 import traceback
 import urllib.error
@@ -15,7 +17,6 @@
 from tempor.log import logger
 
 
-# TODO: Docstring.
 @plugins.register_plugin(name="uci_diabetes", category="prediction.temporal", plugin_type="datasource")
 class UCIDiabetesDataSource(datasource.TemporalPredictionDataSource):
     def __init__(
@@ -25,20 +26,34 @@ def __init__(
         targets: Tuple[str, ...] = ("hypoglycemic_symptoms",),
         **kwargs: Any,
     ) -> None:
+        """UCI diabetes data source.
+
+        See: https://archive.ics.uci.edu/ml/machine-learning-databases/diabetes
+
+        Args:
+            make_regular (bool, optional):
+                Whether to reindex the dataset to have regular timesteps. Defaults to `False`.
+            use_int_index (bool, optional):
+                Whether to use integer index. Defaults to `True`.
+            targets (Tuple[str, ...], optional):
+                The target feature(s). Defaults to ``("hypoglycemic_symptoms",)``.
+            **kwargs (Any):
+                Any additional keyword arguments will be passed to parent constructor.
+        """
         super().__init__(**kwargs)
         self.make_regular = make_regular
         self.use_int_index = use_int_index
         self.targets = targets
 
     @staticmethod
-    def url() -> str:
+    def url() -> str:  # noqa: D102
         return "https://archive.ics.uci.edu/ml/machine-learning-databases/diabetes"
 
     @staticmethod
-    def dataset_dir() -> str:
+    def dataset_dir() -> str:  # noqa: D102
         return str(Path(UCIDiabetesDataSource.data_root_dir) / "uci_diabetes")
 
-    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalPredictionDataset:  # noqa: D102
         download_retries = 3
         download_pause_sec = 5
         for retry in range(download_retries):
diff --git a/src/tempor/datasources/time_to_event/__init__.py b/src/tempor/datasources/time_to_event/__init__.py
index 8d6c58b2..aa3679e7 100644
--- a/src/tempor/datasources/time_to_event/__init__.py
+++ b/src/tempor/datasources/time_to_event/__init__.py
@@ -1,3 +1,5 @@
+"""Package for time to event analysis data sources."""
+
 from tempor.core import plugins
 from tempor.datasources import datasource
 
diff --git a/src/tempor/datasources/time_to_event/plugin_pbc.py b/src/tempor/datasources/time_to_event/plugin_pbc.py
index a3941504..1a563122 100644
--- a/src/tempor/datasources/time_to_event/plugin_pbc.py
+++ b/src/tempor/datasources/time_to_event/plugin_pbc.py
@@ -1,3 +1,5 @@
+"""Module containing the PBC data source."""
+
 import io
 import os
 from typing import Any
@@ -13,25 +15,57 @@
 from tempor.datasources import datasource
 
 
-# TODO: Docstring to explain the dataset.
 @plugins.register_plugin(name="pbc", category="time_to_event", plugin_type="datasource")
 class PBCDataSource(datasource.TimeToEventAnalysisDataSource):
     def __init__(self, **kwargs: Any) -> None:
+        """PBC data source.
+
+        The data is from the PBC2 dataset, as found here:
+        https://search.r-project.org/CRAN/refmans/DynForest/html/pbc2.html
+
+        Actual data obtained from:
+        https://raw.githubusercontent.com/autonlab/auton-survival/cf583e598ec9ab92fa5d510a0ca72d46dfe0706f/dsm/datasets/pbc2.csv
+
+        Temporal features are:
+        - ``"drug"``
+        - ``"ascites"``
+        - ``"hepatomegaly"``
+        - ``"spiders"``
+        - ``"edema"``
+        - ``"histologic"``
+        - ``"serBilir"``
+        - ``"serChol"``
+        - ``"albumin"``
+        - ``"alkaline"``
+        - ``"SGOT"``
+        - ``"platelets"``
+        - ``"prothrombin"``
+        - ``"age"``
+
+        Static features are:
+        - ``"sex"``
+
+        The target is:
+        - ``"status2"`` as found in the ``autonlab/auton-survival`` version.
+
+        Args:
+            **kwargs (Any): Keyword arguments to be passed to the parent class.
+        """
         self.datafile_path = os.path.join(self.dataset_dir(), "pbc2.csv")
         super().__init__(**kwargs)
 
     @staticmethod
-    def dataset_dir() -> str:
+    def dataset_dir() -> str:  # noqa: D102
         return os.path.join(PBCDataSource.data_root_dir, "pbc/")
 
     @staticmethod
-    def url() -> str:
+    def url() -> str:  # noqa: D102
         return (
             "https://raw.githubusercontent.com/autonlab/auton-survival/"
             "cf583e598ec9ab92fa5d510a0ca72d46dfe0706f/dsm/datasets/pbc2.csv"
         )
 
-    def load(self, **kwargs: Any) -> dataset.TimeToEventAnalysisDataset:
+    def load(self, **kwargs: Any) -> dataset.TimeToEventAnalysisDataset:  # noqa: D102
         if os.path.exists(self.datafile_path):
             data = pd.read_csv(self.datafile_path)
         else:
diff --git a/src/tempor/datasources/treatments/__init__.py b/src/tempor/datasources/treatments/__init__.py
index eef95d67..86be0014 100644
--- a/src/tempor/datasources/treatments/__init__.py
+++ b/src/tempor/datasources/treatments/__init__.py
@@ -1,3 +1,5 @@
+"""Package for treatment effects data sources."""
+
 from . import one_off, temporal
 
 __all__ = [
diff --git a/src/tempor/datasources/treatments/one_off/__init__.py b/src/tempor/datasources/treatments/one_off/__init__.py
index 2242aa2e..7e9b6caf 100644
--- a/src/tempor/datasources/treatments/one_off/__init__.py
+++ b/src/tempor/datasources/treatments/one_off/__init__.py
@@ -1,3 +1,5 @@
+"""Package for one-off treatment effects data sources."""
+
 from tempor.core import plugins
 from tempor.datasources import datasource
 
diff --git a/src/tempor/datasources/treatments/one_off/plugin_pkpd.py b/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
index 9cb8476c..cc9a9833 100644
--- a/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
+++ b/src/tempor/datasources/treatments/one_off/plugin_pkpd.py
@@ -1,3 +1,5 @@
+"""Module for the PKPD data source plugin."""
+
 from typing import Any, cast
 
 from clairvoyance2.datasets.simulated.simple_pkpd import simple_pkpd_dataset
@@ -8,7 +10,6 @@
 from tempor.datasources import datasource
 
 
-# TODO: Docstring.
 @plugins.register_plugin(name="pkpd", category="treatments.one_off", plugin_type="datasource")
 class PKPDDataSource(datasource.OneOffTreatmentEffectsDataSource):
     def __init__(
@@ -20,6 +21,22 @@ def __init__(
         random_state: int = 100,
         **kwargs: Any,
     ) -> None:
+        """PKPD data source for one-off treatment effects tasks.
+
+        Adapted from: https://github.com/ZhaozhiQIAN/SyncTwin-NeurIPS-2021
+
+        Args:
+            n_timesteps (int, optional): Number of timesteps. Defaults to ``10``.
+            time_index_treatment_event (int, optional): Time index of the treatment event. Defaults to ``7``.
+            n_control_samples (int, optional): Number of control samples to generate. Defaults to ``20``.
+            n_treated_samples (int, optional): Number of treated samples to generate. Defaults to ``20``.
+            random_state (int, optional): Random state to use. Defaults to ``100``.
+            **kwargs (Any): Any additional keyword arguments will be passed to parent constructor.
+
+        Reference:
+            Qian, Z., Zhang, Y., Bica, I., Wood, A., & van der Schaar, M. (2021). Synctwin: Treatment effect \
+            estimation with longitudinal outcomes. Advances in Neural Information Processing Systems, 34, 3178-3190.
+        """
         super().__init__(**kwargs)
 
         self.n_timesteps = n_timesteps
@@ -29,14 +46,14 @@ def __init__(
         self.random_state = random_state
 
     @staticmethod
-    def url() -> None:
+    def url() -> None:  # noqa: D102
         return None
 
     @staticmethod
-    def dataset_dir() -> None:
+    def dataset_dir() -> None:  # noqa: D102
         return None
 
-    def load(self, **kwargs: Any) -> dataset.OneOffTreatmentEffectsDataset:
+    def load(self, **kwargs: Any) -> dataset.OneOffTreatmentEffectsDataset:  # noqa: D102
         clv_dataset = simple_pkpd_dataset(
             n_timesteps=self.n_timesteps,
             time_index_treatment_event=self.time_index_treatment_event,
diff --git a/src/tempor/datasources/treatments/temporal/__init__.py b/src/tempor/datasources/treatments/temporal/__init__.py
index 7f1e3b72..82ffd34d 100644
--- a/src/tempor/datasources/treatments/temporal/__init__.py
+++ b/src/tempor/datasources/treatments/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Package for temporal treatment effects data sources."""
+
 from tempor.core import plugins
 from tempor.datasources import datasource
 
diff --git a/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py b/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
index 4f4df3c2..9a468acd 100644
--- a/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
+++ b/src/tempor/datasources/treatments/temporal/plugin_dummy_treatments.py
@@ -1,3 +1,5 @@
+"""Module with the dummy data source for temporal treatment effects."""
+
 from typing import Any, Optional, cast
 
 from clairvoyance2.datasets.dummy import dummy_dataset
@@ -52,7 +54,7 @@ def __init__(
                 rather than categorical. Defaults to `None`.
             random_state (int, optional):
                 Random state to use. Defaults to ``12345``.
-            kwargs (Any):
+            **kwargs (Any):
                 Any additional keyword arguments will be passed to `~tempor.datasources.DataSource`.
         """
         super().__init__(**kwargs)
@@ -70,14 +72,14 @@ def __init__(
         self.random_state = random_state
 
     @staticmethod
-    def url() -> None:
+    def url() -> None:  # noqa: D102
         return None
 
     @staticmethod
-    def dataset_dir() -> None:
+    def dataset_dir() -> None:  # noqa: D102
         return None
 
-    def load(self, **kwargs: Any) -> dataset.TemporalTreatmentEffectsDataset:
+    def load(self, **kwargs: Any) -> dataset.TemporalTreatmentEffectsDataset:  # noqa: D102
         clv_dataset = dummy_dataset(
             n_samples=self.n_samples,
             temporal_covariates_n_features=self.temporal_covariates_n_features,
diff --git a/src/tempor/exc/__init__.py b/src/tempor/exc/__init__.py
index f41e9d78..aa5bf070 100644
--- a/src/tempor/exc/__init__.py
+++ b/src/tempor/exc/__init__.py
@@ -1,4 +1,11 @@
+"""Package directory with custom exceptions for TemporAI."""
+
+# pylint: disable=unnecessary-pass
+
+
 class DataValidationException(ValueError):
+    """Exception raised when TemporAI-specific data format validation fails."""
+
     pass
 
 
@@ -7,4 +14,4 @@ class UnsupportedSetupException(RuntimeError):
     e.g. incompatible data format.
     """
 
-    pass  # pylint: disable=unnecessary-pass
+    pass
diff --git a/src/tempor/log/__init__.py b/src/tempor/log/__init__.py
index f9a3d03f..31e028f6 100644
--- a/src/tempor/log/__init__.py
+++ b/src/tempor/log/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory with TemporAI logging."""
+
 from ._custom_logger import logger  # type: ignore [attr-defined]
 
 __all__ = ["logger"]
diff --git a/src/tempor/log/_custom_logger.py b/src/tempor/log/_custom_logger.py
index d2c52586..50da105f 100644
--- a/src/tempor/log/_custom_logger.py
+++ b/src/tempor/log/_custom_logger.py
@@ -1,3 +1,5 @@
+"""Module with custom loguru logger setup for TemporAI."""
+
 import logging
 import os
 import sys
@@ -124,7 +126,14 @@ def _configure_loggers(config: conf.TemporConfig) -> None:
     from loguru import Logger as _Logger
 
     class Logger(_Logger):
+        """A `TYPE_CHECKING`-only subclass of `loguru.Logger` with added `print()` method."""
+
         def print(self, message: str) -> None:
-            pass
+            """A logger method that acts as both logger ``INFO`` message and Python ``print()``.
+
+            Args:
+                message (str): Message to print.
+            """
+            pass  # pylint: disable=unnecessary-pass
 
     logger: Logger  # type: ignore
diff --git a/src/tempor/log/log_helpers.py b/src/tempor/log/log_helpers.py
index 8bd94228..164f33c5 100644
--- a/src/tempor/log/log_helpers.py
+++ b/src/tempor/log/log_helpers.py
@@ -1,3 +1,5 @@
+"""Module with helpers for logging."""
+
 from contextlib import contextmanager
 from typing import Generator
 
@@ -11,6 +13,9 @@ def exc_to_log(message: str = "", reraise: bool = True) -> Generator:
     Args:
         message (str, optional): Log message. Defaults to "".
         reraise (bool, optional): Whether to reraise the exception. Defaults to True.
+
+    Yields:
+        Generator: Context manager.
     """
     try:
         yield
diff --git a/src/tempor/methods/__init__.py b/src/tempor/methods/__init__.py
index 22cdf34a..9ce06b37 100644
--- a/src/tempor/methods/__init__.py
+++ b/src/tempor/methods/__init__.py
@@ -1,3 +1,5 @@
+"""Package directory with all the methods (predictive models, preprocessing etc.) for the project."""
+
 from . import prediction, preprocessing, time_to_event, treatments
 
 __all__ = [
diff --git a/src/tempor/methods/constants.py b/src/tempor/methods/constants.py
new file mode 100644
index 00000000..6d91577c
--- /dev/null
+++ b/src/tempor/methods/constants.py
@@ -0,0 +1,67 @@
+"""Common constants for methods."""
+
+import dataclasses
+from typing import Any, Dict, List, Optional
+
+from clairvoyance2.data import DEFAULT_PADDING_INDICATOR
+
+
+@dataclasses.dataclass
+class Seq2seqParams:
+    # Encoder:
+    encoder_rnn_type: str = "LSTM"
+    """Encoder RNN type. Available options: ``"LSTM"``, ``"GRU"``, ``"RNN"``."""
+    encoder_hidden_size: int = 100
+    """Encoder hidden size."""
+    encoder_num_layers: int = 1
+    """Encoder number of layers."""
+    encoder_bias: bool = True
+    """Encoder bias enabled/disabled."""
+    encoder_dropout: float = 0.0
+    """Encoder dropout values (0.0 to 1.0)"""
+    encoder_bidirectional: bool = False
+    """Encoder bidirectional enabled/disabled."""
+    encoder_nonlinearity: Optional[str] = None
+    """Encoder nonlinearity."""
+    encoder_proj_size: Optional[int] = None
+    """Encoder projection size."""
+    # Decoder:
+    decoder_rnn_type: str = "LSTM"
+    """Decoder RNN type. Available options: ``"LSTM"``, ``"GRU"``, ``"RNN"``."""
+    decoder_hidden_size: int = 100
+    """Decoder hidden size."""
+    decoder_num_layers: int = 1
+    """Decoder number of layers."""
+    decoder_bias: bool = True
+    """Decoder bias enabled/disabled."""
+    decoder_dropout: float = 0.0
+    """Decoder dropout values (0.0 to 1.0)"""
+    decoder_bidirectional: bool = False
+    """Decoder bidirectional enabled/disabled."""
+    decoder_nonlinearity: Optional[str] = None
+    """Decoder nonlinearity."""
+    decoder_proj_size: Optional[int] = None
+    """Decoder projection size."""
+    # Adapter FF NN:
+    adapter_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [50])
+    """Adapter hidden dimensions, as a list of integers corresponding to different layers."""
+    adapter_out_activation: Optional[str] = "Tanh"
+    """Adapter output activation function."""
+    # Predictor FF NN:
+    predictor_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [])
+    """Predictor hidden dimensions, as a list of integers corresponding to different layers."""
+    predictor_out_activation: Optional[str] = None
+    """Predictor output activation function."""
+    # Misc:
+    max_len: Optional[int] = None
+    """Maximum length of the input sequence."""
+    optimizer_str: str = "Adam"
+    """Optimizer type."""
+    optimizer_kwargs: Dict[str, Any] = dataclasses.field(default_factory=lambda: dict(lr=0.01, weight_decay=1e-5))
+    """Optimizer kwargs."""
+    batch_size: int = 32
+    """Batch size."""
+    epochs: int = 100
+    """Number of epochs."""
+    padding_indicator: float = DEFAULT_PADDING_INDICATOR
+    """Padding indicator value."""
diff --git a/src/tempor/methods/core/__init__.py b/src/tempor/methods/core/__init__.py
index 5853222e..de354d95 100644
--- a/src/tempor/methods/core/__init__.py
+++ b/src/tempor/methods/core/__init__.py
@@ -1,14 +1,14 @@
+"""Package directory for the core functionality for the methods, mainly the base classes."""
+
 from tempor.core.plugins import Plugin, PluginLoader, importing, register_plugin, register_plugin_category
 
 from ._base_estimator import BaseEstimator
 from ._base_predictor import BasePredictor
 from ._base_transformer import BaseTransformer
-from ._params import Params
 
 __all__ = [
     "BaseEstimator",
     "importing",
-    "Params",
     "Plugin",
     "PluginLoader",
     "BasePredictor",
diff --git a/src/tempor/methods/core/_base_estimator.py b/src/tempor/methods/core/_base_estimator.py
index ed39d9e2..d342f98c 100644
--- a/src/tempor/methods/core/_base_estimator.py
+++ b/src/tempor/methods/core/_base_estimator.py
@@ -1,3 +1,5 @@
+"""Module with the base estimator class."""
+
 import abc
 import dataclasses
 from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generator, List, Optional, Type
@@ -13,7 +15,7 @@
 from tempor.data import dataset
 from tempor.log import logger
 
-from ._params import Params
+from .params import Params
 
 
 @dataclasses.dataclass
@@ -57,6 +59,24 @@ def root_checks(cls, values: Dict) -> Dict:  # pylint: disable=no-self-argument
         model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
 
     def __init__(self, **params: Any) -> None:
+        """Abstract base class for all estimators.
+
+        Defines some core methods, primarily:
+        - ``fit``: Fit the model to the data.
+        - ``_fit``: The implementation of ``fit``. An abstract method.
+        - ``hyperparameter_space``: The hyperparameter search domain, used for tuning. A static method.
+        - ``sample_hyperparameters``: Sample hyperparameters. A class method.
+
+        The ``ParamsDefinition`` class attribute defines the parameters (in this context, this refers to ``__init__()``
+        method arguments) for the estimator. It must be a dataclass. The parameters will be validated, and the
+        resulting parameters will accessible by the class instance in the ``self.params`` attribute
+        (`omegaconf.DictConfig`).
+
+        Args:
+            **params (Any):
+                The parameters for the estimator, as defined in the ``ParamsDefinition`` class attribute. If not \
+                provided, the default values will be used.
+        """
         Plugin.__init__(self)
         self._fitted = False
         args_validator = self._InitArgsValidator(params=params, ParamsDefinitionClass=self.ParamsDefinition)
@@ -71,12 +91,22 @@ def is_fitted(self) -> bool:
         return self._fitted
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "name", self.name
         yield "category", self.category
         yield "plugin_type", self.plugin_type
         yield "params", omegaconf.OmegaConf.to_container(self.params)
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return rich.pretty.pretty_repr(self)
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
@@ -86,6 +116,16 @@ def fit(
         *args: Any,
         **kwargs: Any,
     ) -> Self:
+        """Fit the method to the data.
+
+        Args:
+            data (dataset.BaseDataset): The dataset to fit the model to.
+            *args (Any): Additional arguments to pass to the ``_fit`` method.
+            **kwargs (Any): Additional keyword arguments to pass to the ``_fit`` method.
+
+        Returns:
+            Self: The fitted model.
+        """
         if not data.fit_ready:
             raise ValueError(
                 f"The dataset was not fit-ready, check that all necessary data components are present:\n{data}"
@@ -99,12 +139,25 @@ def fit(
 
     @abc.abstractmethod
     def _fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # pragma: no cover
-        ...
+        """The implementation of ``fit``. Must be implemented by the child class.
+
+        Args:
+            data (dataset.BaseDataset): The dataset to fit the model to.
+            *args (Any): Additional arguments to receive from the ``fit`` method.
+            **kwargs (Any): Additional keyword arguments to receive from the ``fit`` method.
+
+        Returns:
+            Self: The fitted model.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
     @staticmethod
     @abc.abstractmethod
     def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # pragma: no cover
-        """The hyperparameter search domain, used for tuning."""
+        """The hyperparameter search domain, used for tuning.
+
+        Can provide variadics ``*args`` and ``**kwargs``, these will be received from ``sample_hyperparameters``.
+        """
         ...  # pylint: disable=unnecessary-ellipsis
 
     @classmethod
@@ -123,9 +176,13 @@ def sample_hyperparameters(
             ``.sample_hyperparameters(trial, ...)`` or ``.sample_hyperparameters(..., trial=trial, ...)``.
 
         Args:
+            *args (Any):
+                Additional arguments to pass to the ``hyperparameter_space`` method.
             override (Optional[List[Params]], optional):
-                If this is not `None`, hyperparameters will be sampled from this list, rather than from those defined\
-                    in the ``hyperparameter_space`` method. Defaults to `None`.
+                If this is not `None`, hyperparameters will be sampled from this list, rather than from those defined \
+                in the ``hyperparameter_space`` method. Defaults to `None`.
+            **kwargs (Any):
+                Additional keyword arguments to pass to the ``hyperparameter_space`` method.
 
         Returns:
             Dict[str, Any]: _description_
diff --git a/src/tempor/methods/core/_base_predictor.py b/src/tempor/methods/core/_base_predictor.py
index b88c5c60..e6651841 100644
--- a/src/tempor/methods/core/_base_predictor.py
+++ b/src/tempor/methods/core/_base_predictor.py
@@ -1,3 +1,5 @@
+"""Module with the base predictor class."""
+
 import abc
 from typing import Any
 
@@ -12,6 +14,14 @@
 
 class BasePredictor(estimator.BaseEstimator):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Abstract base class for all predictors.
+
+        Defines some core methods, primarily:
+        - ``predict``: Predicts the target variable for the given data.
+        - ``predict_proba``: Predicts the probability of the target variable for the given data.
+        - ``predict_counterfactuals``: Predicts the counterfactuals for the given data.
+        - The `_` versions of the above methods are the implementations of the above methods in the derived classes.
+        """
         super().__init__(**params)
 
     def predict(
@@ -19,7 +29,17 @@ def predict(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> Any:  # TODO: Narrow down output formats later.
+    ) -> Any:
+        """Predicts the target variable for the given data.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments passed to the implementation (``_predict``).
+            **kwargs (Any): Additional keyword arguments passed to the implementation (``_predict``).
+
+        Returns:
+            Any: The predictions.
+        """
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
         if not data.predict_ready:
@@ -37,7 +57,17 @@ def predict_proba(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> Any:  # TODO: Narrow down output formats later.
+    ) -> Any:
+        """Predicts the probability of the target variable for the given data.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments passed to the implementation (``_predict_proba``).
+            **kwargs (Any): Additional keyword arguments passed to the implementation (``_predict_proba``).
+
+        Returns:
+            Any: The predicted probabilities.
+        """
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
         if not data.predict_ready:
@@ -55,7 +85,17 @@ def predict_counterfactuals(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> Any:  # TODO: Narrow down output formats later.
+    ) -> Any:
+        """Predicts the counterfactuals for the given data.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments passed to the implementation (``_predict_counterfactuals``).
+            **kwargs (Any): Additional keyword arguments passed to the implementation (``_predict_counterfactuals``).
+
+        Returns:
+            Any: The predicted counterfactuals.
+        """
         if not self.is_fitted:
             raise ValueError("The model was not fitted, call `fit` first")
         if not data.predict_ready:
@@ -76,15 +116,56 @@ def fit_predict(
         *args: Any,
         **kwargs: Any,
     ) -> Any:
+        """Fit the model to the data and then predict on the same data. Equivalent to calling ``fit`` and then
+        ``predict``.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to fit and predict on.
+            *args (Any): Additional positional arguments passed to the implementations (``_fit`` and ``_predict``).
+            **kwargs (Any): Additional keyword arguments passed to the implementations (``_fit`` and ``_predict``).
+
+        Returns:
+            Any: The predictions.
+        """
         self.fit(data, *args, **kwargs)
         return self.predict(data, *args, **kwargs)
 
     @abc.abstractmethod
     def _predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:  # pragma: no cover
-        ...
+        """The implementation of ``predict``. Must be implemented by the child class.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments received by the implementation.
+            **kwargs (Any): Additional keyword arguments received by the implementation.
+
+        Returns:
+            Any: The predictions.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
     def _predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
+        """The implementation of ``predict_proba``. Must be implemented by the child class.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments received by the implementation.
+            **kwargs (Any): Additional keyword arguments received by the implementation.
+
+        Returns:
+            Any: The predicted probabilities.
+        """
         raise NotImplementedError("`predict_proba` is supported only for classification tasks")
 
     def _predict_counterfactuals(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
+        """The implementation of ``predict_counterfactuals``. Must be implemented by the child class.
+
+        Args:
+            data (dataset.PredictiveDataset): The dataset to predict on.
+            *args (Any): Additional positional arguments received by the implementation.
+            **kwargs (Any): Additional keyword arguments received by the implementation.
+
+        Returns:
+            Any: The predicted counterfactuals.
+        """
         raise NotImplementedError("`predict_counterfactuals` is supported only for treatments tasks")
diff --git a/src/tempor/methods/core/_base_transformer.py b/src/tempor/methods/core/_base_transformer.py
index 68354ab7..2ec06d5b 100644
--- a/src/tempor/methods/core/_base_transformer.py
+++ b/src/tempor/methods/core/_base_transformer.py
@@ -1,3 +1,5 @@
+"""Module with the base transformer class."""
+
 import abc
 from typing import Any
 
@@ -12,6 +14,12 @@
 
 class BaseTransformer(estimator.BaseEstimator):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Abstract base class for all transformers.
+
+        Defines some core methods, primarily:
+        - ``transform``: Transforms the given data.
+        - ``_transform``: The implementation of the above method in the derived classes.
+        """
         super().__init__(**params)
 
     def transform(
@@ -20,6 +28,16 @@ def transform(
         *args: Any,
         **kwargs: Any,
     ) -> Any:
+        """Transforms the given data.
+
+        Args:
+            data (dataset.BaseDataset): The dataset to transform.
+            *args (Any): Additional positional arguments passed to the implementation (``_transform``).
+            **kwargs (Any): Additional keyword arguments passed to the implementation (``_transform``).
+
+        Returns:
+            Any: The transformed data.
+        """
         logger.debug(f"Calling _transform() implementation on {self.__class__.__name__}")
         transformed_data = self._transform(data, *args, **kwargs)
 
@@ -32,6 +50,16 @@ def fit_transform(
         *args: Any,
         **kwargs: Any,
     ) -> dataset.BaseDataset:
+        """Fit the method to the data and transform it. Equivalent to calling ``fit`` and then ``transform``.
+
+        Args:
+            data (dataset.BaseDataset): The dataset to fit and transform.
+            *args (Any): Additional arguments to pass to the ``_fit`` and ``_transform`` methods.
+            **kwargs (Any): Additional keyword arguments to pass to the ``_fit`` and ``_transform`` methods.
+
+        Returns:
+            dataset.BaseDataset: The transformed dataset.
+        """
         self.fit(data, *args, **kwargs)
         return self.transform(data, *args, **kwargs)
 
@@ -39,4 +67,14 @@ def fit_transform(
     def _transform(
         self, data: dataset.BaseDataset, *args: Any, **kwargs: Any
     ) -> dataset.BaseDataset:  # pragma: no cover
+        """The implementation of ``transform``. Must be implemented by the child class.
+
+        Args:
+            data (dataset.BaseDataset): The dataset to transform.
+            *args (Any): Additional positional arguments received by the implementation.
+            **kwargs (Any): Additional keyword arguments received by the implementation.
+
+        Returns:
+            dataset.BaseDataset: The transformed dataset.
+        """
         ...
diff --git a/src/tempor/methods/core/_params.py b/src/tempor/methods/core/params.py
similarity index 58%
rename from src/tempor/methods/core/_params.py
rename to src/tempor/methods/core/params.py
index 087d81e3..3d6ed5a6 100644
--- a/src/tempor/methods/core/_params.py
+++ b/src/tempor/methods/core/params.py
@@ -1,3 +1,5 @@
+"""Module defining `Params` classes used for sampling in hyperparameter tuning."""
+
 import abc
 import random
 from typing import Any, Generator, List, Optional, Tuple
@@ -9,9 +11,14 @@
 
 
 class Params(abc.ABC):
-    """Helper for describing the hyperparameters for each estimator."""
-
     def __init__(self, name: str, bounds: Tuple[Any, Any]) -> None:
+        """Abstract base class for all hyperparameter sampling classes. A helper for describing the hyperparameters for
+        each estimator.
+
+        Args:
+            name (str): Hyperparameter name.
+            bounds (Tuple[Any, Any]): The bounds (lower, higher) of the hyperparameter.
+        """
         if name in RESERVED_ARG_NAMES:
             raise ValueError(f"The hyperparameter name '{name}' is not allowed, as it is a special argument")
         self.name = name
@@ -19,9 +26,23 @@ def __init__(self, name: str, bounds: Tuple[Any, Any]) -> None:
 
     @abc.abstractmethod
     def get(self) -> List[Any]:  # pragma: no cover
-        ...
+        """Returns the hyperparameter name and properties as a list.
+
+        Returns:
+            List[Any]: The hyperparameter name and properties as a list.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
     def sample(self, trial: Optional[Trial] = None) -> Any:
+        """Sample the hyperparameter. If `trial` is not `None`, dispatch to ``_sample_optuna_trial``. Otherwise,
+        dispatch to ``_sample_default``.
+
+        Args:
+            trial (Optional[Trial], optional): Trial object, e.g `optuna.trial`. Defaults to None.
+
+        Returns:
+            Any: The sampled hyperparameter.
+        """
         if trial is not None:
             return self._sample_optuna_trial(trial)
         else:
@@ -37,22 +58,36 @@ def _sample_default(self) -> Any:  # pragma: no cover
         ...
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "name", self.name
         yield "bounds", self.bounds
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return rich.pretty.pretty_repr(self)
 
 
 class CategoricalParams(Params):
-    """Sample from a categorical distribution."""
-
     def __init__(self, name: str, choices: List[Any]) -> None:
+        """Sample from a categorical distribution.
+
+        Args:
+            name (str): Hyperparameter name.
+            choices (List[Any]): The choices to sample from.
+        """
         super().__init__(name, (min(choices), max(choices)))
         self.name = name
         self.choices = choices
 
-    def get(self) -> List[Any]:
+    def get(self) -> List[Any]:  # noqa: D102
         return [self.name, self.choices]
 
     def _sample_optuna_trial(self, trial: Trial) -> Any:
@@ -62,14 +97,24 @@ def _sample_default(self) -> Any:
         return random.SystemRandom().choice(self.choices)
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "name", self.name
         yield "choices", self.choices
 
 
 class FloatParams(Params):
-    """Sample from a float distribution."""
-
     def __init__(self, name: str, low: float, high: float) -> None:
+        """Sample from a float distribution.
+
+        Args:
+            name (str): Hyperparameter name.
+            low (float): Lower bound.
+            high (float): Upper bound.
+        """
         low = float(low)
         high = float(high)
 
@@ -78,7 +123,7 @@ def __init__(self, name: str, low: float, high: float) -> None:
         self.low = low
         self.high = high
 
-    def get(self) -> List[Any]:
+    def get(self) -> List[Any]:  # noqa: D102
         return [self.name, self.low, self.high]
 
     def _sample_optuna_trial(self, trial: Trial) -> Any:
@@ -88,15 +133,26 @@ def _sample_default(self) -> Any:
         return random.uniform(self.low, self.high)  # nosec
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "name", self.name
         yield "low", self.low
         yield "high", self.high
 
 
 class IntegerParams(Params):
-    """Sample from an integer distribution."""
-
     def __init__(self, name: str, low: int, high: int, step: int = 1) -> None:
+        """Sample from an integer distribution.
+
+        Args:
+            name (str): Hyperparameter name.
+            low (int): Lower bound.
+            high (int): Upper bound.
+            step (int, optional): Step. Defaults to ``1``.
+        """
         self.low = low
         self.high = high
         self.step = step
@@ -108,7 +164,7 @@ def __init__(self, name: str, low: int, high: int, step: int = 1) -> None:
         self.step = step
         self.choices = [val for val in range(low, high + 1, step)]
 
-    def get(self) -> List[Any]:
+    def get(self) -> List[Any]:  # noqa: D102
         return [self.name, self.low, self.high, self.step]
 
     def _sample_optuna_trial(self, trial: Trial) -> Any:
@@ -118,6 +174,11 @@ def _sample_default(self) -> Any:
         return random.SystemRandom().choice(self.choices)
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "name", self.name
         yield "low", self.low
         yield "high", self.high
diff --git a/src/tempor/methods/pipeline/__init__.py b/src/tempor/methods/pipeline/__init__.py
index 580a07b7..3f4339f2 100644
--- a/src/tempor/methods/pipeline/__init__.py
+++ b/src/tempor/methods/pipeline/__init__.py
@@ -1,3 +1,5 @@
+"""A package directory for the pipeline functionality."""
+
 import abc
 from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple, Type
 
@@ -8,7 +10,7 @@
 from tempor import plugin_loader
 from tempor.data import dataset
 from tempor.log import logger
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.methods.prediction.temporal.classification import BaseTemporalClassifier
@@ -58,7 +60,9 @@ def __init__(self, plugin_params: Optional[Dict[str, Dict]] = None, **kwargs: An
 
         Args:
             plugin_params (Optional[Dict[str, Dict]], optional):
-                A dictionary like ``{"plugin_1_name": {"init_param_1": value, ...}, ...}``. Defaults to None.
+                A dictionary like ``{"plugin_1_name": {"init_param_1": value, ...}, ...}``. Defaults to `None`.
+            **kwargs (Any):
+                Any keyword arguments.
         """
         raise NotImplementedError("Not implemented")
 
@@ -67,6 +71,9 @@ def pipeline_seq(*args: Any) -> str:  # pragma: no cover
         """Get a string representation of the pipeline, stating each stage plugin, e.g. like:
         ``'preprocessing.imputation.temporal.bfill->...->prediction.one_off.classification.nn_classifier'``
 
+        Args:
+            *args (Any): Any positional arguments.
+
         Returns:
             str: String representation of the pipeline.
         """
@@ -77,6 +84,10 @@ def hyperparameter_space(*args: Any, **kwargs: Any) -> Dict[str, List[Params]]:
         """The pipeline version of the estimator static method of the same name. All the hyperparameters of the
         different stages will be returned.
 
+        Args:
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
+
         Returns:
             Dict[str, List[Params]]: A dictionary with each stage plugin names as keys and corresponding hyperparameter\
                 space (``List[Params]``) as values.
@@ -89,6 +100,8 @@ def hyperparameter_space_for_step(name: str, *args: Any, **kwargs: Any) -> List[
 
         Args:
             name (str): Name of the pipeline step (i.e. the name of the underlying plugin).
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
 
         Returns:
             List[Params]: the hyperparameter space for the step of the pipeline.
@@ -101,6 +114,12 @@ def sample_hyperparameters(
     ) -> Dict[str, Any]:  # pragma: no cover
         """The pipeline version of the estimator method of the same name. Returns a hyperparameter sample.
 
+        Args:
+            *args (Any): Any positional arguments.
+            override (Optional[List[Params]], optional): A list of hyperparameter samples to override the default\
+                sampling. Defaults to `None`.
+            **kwargs (Any): Any keyword arguments.
+
         Returns:
             Dict[str, Any]: a dictionary with hyperparameter names as keys and corresponding hyperparameter samples\
                 as values.
@@ -116,6 +135,8 @@ def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  #
 
         Args:
             data (dataset.BaseDataset): Input dataset.
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
 
         Returns:
             Self: Returns the fitted pipeline itself.
@@ -128,6 +149,8 @@ def predict(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) ->
 
         Args:
             data (dataset.PredictiveDataset): Input dataset.
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
 
         Returns:
             Any: the same return type as the final step of the pipeline.
@@ -140,6 +163,8 @@ def predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: A
 
         Args:
             data (dataset.PredictiveDataset): Input dataset.
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
 
         Returns:
             Any: the same return type as the final step of the pipeline.
@@ -154,6 +179,8 @@ def predict_counterfactuals(
 
         Args:
             data (dataset.PredictiveDataset): Input dataset.
+            *args (Any): Any positional arguments.
+            **kwargs (Any): Any keyword arguments.
 
         Returns:
             Any: the same return type as the final step of the pipeline.
@@ -162,25 +189,54 @@ def predict_counterfactuals(
 
     @property
     def predictor_category(self) -> str:
+        """Return the ``category`` of the final step of the pipeline.
+
+        Returns:
+            str: The ``category`` of the final step of the pipeline.
+        """
         return self.plugin_types[-1].category
 
     @property
     def params(self) -> Dict[str, omegaconf.DictConfig]:
+        """Return a dictionary with the parameters (``.params``) of each step of the pipeline (keyed by the stage name).
+
+        Returns:
+            Dict[str, omegaconf.DictConfig]: The requisite dictionary.
+        """
         out = dict()
         for p in self.stages:
             out[p.name] = p.params
         return out
 
     def __rich_repr__(self) -> Generator:
+        """A `rich` representation of the class.
+
+        Yields:
+            Generator: The fields and their values fed to `rich`.
+        """
         yield "pipeline_seq", self.pipeline_seq()
         yield "predictor_category", self.predictor_category
         yield "params", {k: omegaconf.OmegaConf.to_container(v) for k, v in self.params.items()}
 
     def __repr__(self) -> str:
+        """The `repr()` representation of the class.
+
+        Returns:
+            str: The representation.
+        """
         return rich.pretty.pretty_repr(self)
 
 
 def prepend_base(base: Type, bases: List[Type]) -> List[Type]:
+    """Prepend a base class to a list of base classes, if it is not already present.
+
+    Args:
+        base (Type): Base class to prepend.
+        bases (List[Type]): List of base classes.
+
+    Returns:
+        List[Type]: The list of base classes, with the base class prepended.
+    """
     if base in bases:
         bases_final = bases
     else:
@@ -189,11 +245,23 @@ def prepend_base(base: Type, bases: List[Type]) -> List[Type]:
 
 
 def raise_not_implemented(*args: Any, **kwargs: Any) -> NoReturn:
+    """Raise a ``NotImplementedError`` if a method like ``_fit/predict/...`` is not implemented.
+
+    Args:
+        *args (Any): Any positional arguments.
+        **kwargs (Any): Any keyword arguments.
+
+    Raises:
+        NotImplementedError: The requisite error.
+
+    Returns:
+        NoReturn: Does not return, raises error.
+    """
     raise NotImplementedError("The `{_fit/predict/...}` methods are not implemented for the pipelines")
 
 
 class PipelineMeta(abc.ABCMeta):
-    def __new__(
+    def __new__(  # noqa: DOC102, DOC103
         cls: Any,
         __name: str,
         __bases: Tuple[type, ...],
@@ -201,6 +269,25 @@ def __new__(
         plugins: Tuple[Type, ...] = tuple(),
         **kwds: Any,
     ) -> Any:
+        """The metaclass ``__new__`` method for defining the pipeline classes.
+
+        Args:
+            cls (Any):
+                The class.
+            __name (str):
+                The name of the class.
+            __bases (Tuple[type, ...]):
+                The list of base classes.
+            __namespace (Dict[str, Any]):
+                The namespace of the class.
+            plugins (Tuple[Type, ...], optional):
+                The list of pipeline plugins. Defaults to ``tuple()``.
+            **kwds (Any):
+                Any keyword arguments to be passed to ``super().__new__``.
+
+        Returns:
+            Any: The class.
+        """
         logger.debug(f"Creating pipeline defined by steps:\n{plugins}")
 
         # Constructor:
@@ -237,6 +324,15 @@ def __new__(
 
     @staticmethod
     def parse_bases(bases: Tuple[type, ...], plugins: Tuple[Type, ...]) -> Tuple[type, ...]:
+        """Append the applicable base classes (those that are the last pipeline step is a subclass of) to ``bases``.
+
+        Args:
+            bases (Tuple[type, ...]): List of base classes.
+            plugins (Tuple[Type, ...]): The list of the pipeline's plugins.
+
+        Returns:
+            Tuple[type, ...]: The appended list of base classes.
+        """
         bases_final: List[Type] = list(bases)
 
         if len(plugins) > 0:
diff --git a/src/tempor/methods/pipeline/generators.py b/src/tempor/methods/pipeline/generators.py
index 3ab9e7bc..6b79cd5e 100644
--- a/src/tempor/methods/pipeline/generators.py
+++ b/src/tempor/methods/pipeline/generators.py
@@ -1,12 +1,23 @@
+"""Generator functions for the pipeline class creation used by `~tempor.methods.pipeline.PipelineMeta`."""
+
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type
 
 from tempor.data import dataset
 
 if TYPE_CHECKING:  # pragma: no cover
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
 
 
 def _generate_pipeline_seq_impl(plugins: Tuple[Type, ...]) -> Callable:
+    """A "meta-function" to generate a pipeline sequence string.
+
+    Args:
+        plugins (Tuple[Type, ...]): Sequence of plugins that form the pipeline.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def pipeline_seq_impl(*args: Any) -> str:  # pylint: disable=unused-argument
         return "->".join(p.full_name() for p in plugins)
 
@@ -14,6 +25,15 @@ def pipeline_seq_impl(*args: Any) -> str:  # pylint: disable=unused-argument
 
 
 def _generate_hyperparameter_space_impl(plugins: Tuple[Type, ...]) -> Callable:
+    """A "meta-function" to generate a hyperparameter space dictionary.
+
+    Args:
+        plugins (Tuple[Type, ...]): Sequence of plugins that form the pipeline.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def hyperparameter_space_impl(*args: Any, **kwargs: Any) -> Dict:
         out = {}
         for p in plugins:
@@ -24,6 +44,15 @@ def hyperparameter_space_impl(*args: Any, **kwargs: Any) -> Dict:
 
 
 def _generate_hyperparameter_space_for_step_impl(plugins: Tuple[Type, ...]) -> Callable:
+    """A "meta-function" to generate a hyperparameter space dictionary for a specific step.
+
+    Args:
+        plugins (Tuple[Type, ...]): Sequence of plugins that form the pipeline.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def hyperparameter_space_for_step_impl(step: str, *args: Any, **kwargs: Any) -> Dict:
         for p in plugins:
             if p.name == step:
@@ -34,6 +63,15 @@ def hyperparameter_space_for_step_impl(step: str, *args: Any, **kwargs: Any) ->
 
 
 def _generate_sample_hyperparameters_impl(plugins: Tuple[Type, ...]) -> Callable:
+    """A "meta-function" to sample hyperparameters for a specific step.
+
+    Args:
+        plugins (Tuple[Type, ...]): Sequence of plugins that form the pipeline.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def sample_hyperparameters_impl(*args: Any, override: Optional[List["Params"]] = None, **kwargs: Any) -> Dict:
         sample: dict = {}
         for p in plugins:
@@ -45,6 +83,12 @@ def sample_hyperparameters_impl(*args: Any, override: Optional[List["Params"]] =
 
 
 def _generate_constructor() -> Callable:
+    """A "meta-function" to generate the constructor for the pipeline class.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def _sanity_checks(plugins: Tuple[Type, ...]) -> None:
         if len(plugins) == 0:
             raise RuntimeError("Invalid empty pipeline.")
@@ -78,6 +122,12 @@ def init_impl(self: Any, plugin_params: Optional[Dict[str, Dict]] = None) -> Non
 
 
 def _generate_fit() -> Callable:
+    """A "meta-function" to generate the fit method for the pipeline class.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def fit_impl(self: Any, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Any:
         local_X = data
         for stage in self.stages[:-1]:
@@ -91,6 +141,12 @@ def fit_impl(self: Any, data: dataset.BaseDataset, *args: Any, **kwargs: Any) ->
 
 
 def _generate_predict() -> Callable:
+    """A "meta-function" to generate the predict method for the pipeline class.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def predict_impl(self: Any, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         local_X = data
         for stage in self.stages[:-1]:
@@ -102,6 +158,12 @@ def predict_impl(self: Any, data: dataset.PredictiveDataset, *args: Any, **kwarg
 
 
 def _generate_predict_proba() -> Callable:
+    """A "meta-function" to generate the predict_proba method for the pipeline class.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def predict_proba_impl(self: Any, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         local_X = data
         for stage in self.stages[:-1]:
@@ -113,6 +175,12 @@ def predict_proba_impl(self: Any, data: dataset.PredictiveDataset, *args: Any, *
 
 
 def _generate_predict_counterfactuals() -> Callable:
+    """A "meta-function" to generate the predict_counterfactuals method for the pipeline class.
+
+    Returns:
+        Callable: The function that does this job.
+    """
+
     def predict_counterfactuals_impl(self: Any, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
         local_X = data
         for stage in self.stages[:-1]:
diff --git a/src/tempor/methods/prediction/__init__.py b/src/tempor/methods/prediction/__init__.py
index eef95d67..e63a0f6c 100644
--- a/src/tempor/methods/prediction/__init__.py
+++ b/src/tempor/methods/prediction/__init__.py
@@ -1,3 +1,5 @@
+"""Temporal prediction methods."""
+
 from . import one_off, temporal
 
 __all__ = [
diff --git a/src/tempor/methods/prediction/one_off/__init__.py b/src/tempor/methods/prediction/one_off/__init__.py
index ec2cd3ba..0e6d60bc 100644
--- a/src/tempor/methods/prediction/one_off/__init__.py
+++ b/src/tempor/methods/prediction/one_off/__init__.py
@@ -1,3 +1,5 @@
+"""One-off prediction methods."""
+
 from . import classification, regression
 
 __all__ = [
diff --git a/src/tempor/methods/prediction/one_off/classification/__init__.py b/src/tempor/methods/prediction/one_off/classification/__init__.py
index b6a8b724..c7947c83 100644
--- a/src/tempor/methods/prediction/one_off/classification/__init__.py
+++ b/src/tempor/methods/prediction/one_off/classification/__init__.py
@@ -1,3 +1,5 @@
+"""One-off classification estimators."""
+
 import abc
 from typing import Any, Tuple
 
@@ -11,6 +13,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check if data is a one-off prediction dataset.
+
+    Args:
+        data (Any): The data.
+
+    Raises:
+        TypeError: If data is not a one-off prediction dataset.
+    """
     if not isinstance(data, dataset.OneOffPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a one-off classification estimator to be "
@@ -20,9 +30,15 @@ def check_data_class(data: Any) -> None:
 
 class BaseOneOffClassifier(methods_core.BasePredictor):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Base class for one-off classification estimators.
+
+        Args:
+            **params (Any):
+                Parameters as defined in :class:`BasePredictorParams`.
+        """
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -33,7 +49,7 @@ def predict(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> samples.StaticSamples:
+    ) -> samples.StaticSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
@@ -43,7 +59,7 @@ def predict_proba(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> samples.StaticSamples:
+    ) -> samples.StaticSamples:  # noqa: D102
         check_data_class(data)
         return super().predict_proba(data, *args, **kwargs)
 
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
index 427a3088..02ac2389 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_cde_classifier.py
@@ -1,3 +1,5 @@
+"""One-off classification plugin based on Neural Controlled Differential Equations for Irregular Time Series."""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -6,8 +8,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -69,7 +70,7 @@ def __init__(self, **params: Any) -> None:
         """Neural Controlled Differential Equations for Irregular Time Series.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`CDEClassifierParams`.
 
         Example:
@@ -172,7 +173,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
index 36f3ff69..bd079923 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_laplace_classifier.py
@@ -1,3 +1,7 @@
+"""One-off classification plugin based on
+"Neural Laplace: Learning diverse classes of differential equations in the Laplace domain".
+"""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -6,8 +10,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -71,7 +74,7 @@ def __init__(self, **params: Any) -> None:
         Riemann stereographic projection for better global representation of the complex Laplace domain.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`LaplaceODEClassifierParams`.
 
         Example:
@@ -175,7 +178,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
index 68389483..07a49466 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_nn_classifier.py
@@ -1,3 +1,5 @@
+"""One-off classification plugin based on Neural Networks."""
+
 import dataclasses
 from typing import TYPE_CHECKING, Any, List, Optional
 
@@ -6,8 +8,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -67,7 +68,7 @@ def __init__(self, **params: Any) -> None:
         """Neural-net classifier.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`NeuralNetClassifierParams`.
 
         Example:
@@ -168,7 +169,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_static_units_hidden", low=100, high=1000),
             IntegerParams(name="n_static_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py b/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
index ce834689..0cd25cb4 100644
--- a/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
+++ b/src/tempor/methods/prediction/one_off/classification/plugin_ode_classifier.py
@@ -1,3 +1,5 @@
+"""One-off classification plugin based on "Neural Ordinary Differential Equations"."""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -6,8 +8,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -69,7 +70,7 @@ def __init__(self, **params: Any) -> None:
         """Classifier based on ordinary differential equation (ODE) solvers.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`ODEClassifierParams`.
 
         Example:
@@ -88,8 +89,8 @@ def __init__(self, **params: Any) -> None:
             >>> assert model.predict(dataset).numpy().shape == (len(dataset), 1)
 
         References:
-            "Neural Controlled Differential Equations for Irregular Time Series", Patrick Kidger, James Morrill,
-            James Foster, Terry Lyons.
+            "Neural Ordinary Differential Equations", Ricky T. Q. Chen, Yulia Rubanova, Jesse Bettencourt,
+            David Duvenaud.
         """
         super().__init__(**params)
 
@@ -172,7 +173,7 @@ def _predict_proba(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/__init__.py b/src/tempor/methods/prediction/one_off/regression/__init__.py
index c3bc73bc..e3c9d5e7 100644
--- a/src/tempor/methods/prediction/one_off/regression/__init__.py
+++ b/src/tempor/methods/prediction/one_off/regression/__init__.py
@@ -1,3 +1,5 @@
+"""One-off regression estimators."""
+
 import abc
 from typing import Any, Tuple
 
@@ -11,6 +13,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check if data is a one-off prediction dataset.
+
+    Args:
+        data (Any): The data.
+
+    Raises:
+        TypeError: If data is not a one-off prediction dataset.
+    """
     if not isinstance(data, dataset.OneOffPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a one-off regression estimator to be "
@@ -20,9 +30,15 @@ def check_data_class(data: Any) -> None:
 
 class BaseOneOffRegressor(methods_core.BasePredictor):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Base class for one-off regression estimators.
+
+        Args:
+            **params (Any):
+                Parameters as defined in :class:`BasePredictorParams`.
+        """
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -33,7 +49,7 @@ def predict(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> samples.StaticSamples:
+    ) -> samples.StaticSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
index 8e8f12d2..c81975cf 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_cde_regressor.py
@@ -1,3 +1,5 @@
+"""One-off regression plugin based on Neural Controlled Differential Equations for Irregular Time Series."""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -5,8 +7,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -68,7 +69,7 @@ def __init__(self, **params: Any) -> None:
         """Neural Controlled Differential Equations for Irregular Time Series.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`CDERegressorParams`.
 
         Example:
@@ -154,7 +155,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
index 874bcddc..aab25b16 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_laplace_regressor.py
@@ -1,3 +1,7 @@
+"""One-off regression plugin based on
+"Neural Laplace: Learning diverse classes of differential equations in the Laplace domain".
+"""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -5,8 +9,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -70,7 +73,7 @@ def __init__(self, **params: Any) -> None:
         Riemann stereographic projection for better global representation of the complex Laplace domain.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`LaplaceODERegressorParams`.
 
         Example:
@@ -157,7 +160,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
index 0de85d60..db57e501 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_nn_regressor.py
@@ -1,3 +1,5 @@
+"""One-off regression plugin based on Neural Networks."""
+
 import dataclasses
 from typing import TYPE_CHECKING, Any, List, Optional
 
@@ -5,8 +7,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -66,7 +67,7 @@ def __init__(self, **params: Any) -> None:
         """Neural-net regressor.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`NeuralNetRegressorParams`.
 
         Example:
@@ -149,7 +150,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_static_units_hidden", low=100, high=1000),
             IntegerParams(name="n_static_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py b/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
index f82fb70b..cf572333 100644
--- a/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
+++ b/src/tempor/methods/prediction/one_off/regression/plugin_ode_regressor.py
@@ -1,3 +1,5 @@
+"""One-off regression plugin based on "Neural Ordinary Differential Equations"."""
+
 import dataclasses
 from typing import Any, List, Optional
 
@@ -5,8 +7,7 @@
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.one_off.regression import BaseOneOffRegressor
 from tempor.models import utils as model_utils
 from tempor.models.constants import Nonlin, Samp
@@ -68,7 +69,7 @@ def __init__(self, **params: Any) -> None:
         """Regressor based on ordinary differential equation (ODE) solvers.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`ODERegressorParams`.
 
         Example:
@@ -87,8 +88,8 @@ def __init__(self, **params: Any) -> None:
             >>> assert model.predict(dataset).numpy().shape == (len(dataset), 1)
 
         References:
-            "Neural Controlled Differential Equations for Irregular Time Series", Patrick Kidger, James Morrill,
-            James Foster, Terry Lyons.
+            "Neural Ordinary Differential Equations", Ricky T. Q. Chen, Yulia Rubanova, Jesse Bettencourt,
+            David Duvenaud.
         """
         super().__init__(**params)
 
@@ -154,7 +155,7 @@ def _predict(
         return samples.StaticSamples.from_numpy(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=100, high=1000),
             IntegerParams(name="n_layers_hidden", low=1, high=5),
diff --git a/src/tempor/methods/prediction/temporal/__init__.py b/src/tempor/methods/prediction/temporal/__init__.py
index ec2cd3ba..091e638b 100644
--- a/src/tempor/methods/prediction/temporal/__init__.py
+++ b/src/tempor/methods/prediction/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Temporal prediction methods."""
+
 from . import classification, regression
 
 __all__ = [
diff --git a/src/tempor/methods/prediction/temporal/classification/__init__.py b/src/tempor/methods/prediction/temporal/classification/__init__.py
index f2c61da8..70faca48 100644
--- a/src/tempor/methods/prediction/temporal/classification/__init__.py
+++ b/src/tempor/methods/prediction/temporal/classification/__init__.py
@@ -1,3 +1,5 @@
+"""Temporal classification estimators."""
+
 import abc
 from typing import Any
 
@@ -10,6 +12,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check that the passed data is a temporal prediction dataset.
+
+    Args:
+        data (Any): The data to check.
+
+    Raises:
+        TypeError: If the data is not a temporal prediction dataset.
+    """
     if not isinstance(data, dataset.TemporalPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a temporal classification estimator to be "
@@ -19,9 +29,15 @@ def check_data_class(data: Any) -> None:
 
 class BaseTemporalClassifier(methods_core.BasePredictor):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Base class for temporal classification estimators.
+
+        Args:
+            **params (Any):
+                Parameters and defaults as defined in :class:`BasePredictorParams`.
+        """
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -34,7 +50,7 @@ def predict(  # type: ignore [override]  # pylint: disable=arguments-differ
         *args: Any,
         time_delta: int = 1,
         **kwargs: Any,
-    ) -> samples.TimeSeriesSamples:
+    ) -> samples.TimeSeriesSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
@@ -46,7 +62,7 @@ def predict_proba(  # type: ignore [override]  # pylint: disable=arguments-diffe
         *args: Any,
         time_delta: int = 1,
         **kwargs: Any,
-    ) -> samples.TimeSeriesSamples:
+    ) -> samples.TimeSeriesSamples:  # noqa: D102
         check_data_class(data)
         return super().predict_proba(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
diff --git a/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py b/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
index 12445606..8bef72e7 100644
--- a/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
+++ b/src/tempor/methods/prediction/temporal/classification/plugin_seq2seq_classifier.py
@@ -1,59 +1,23 @@
-import dataclasses
-from typing import Any, Dict, List, Optional, cast
+"""Temporal classification estimator based on Seq2Seq model."""
+
+from typing import Any, List, cast
 
 import clairvoyance2.data.dataformat as cl_dataformat
-from clairvoyance2.data import DEFAULT_PADDING_INDICATOR
 from clairvoyance2.prediction.seq2seq import Seq2SeqClassifier, TimeIndexHorizon
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.constants import Seq2seqParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.temporal.classification import BaseTemporalClassifier
 
 
-@dataclasses.dataclass
-class Seq2seqClassifierParams:
-    # TODO: Docstrings.
-    # Encoder:
-    encoder_rnn_type: str = "LSTM"
-    encoder_hidden_size: int = 100
-    encoder_num_layers: int = 1
-    encoder_bias: bool = True
-    encoder_dropout: float = 0.0
-    encoder_bidirectional: bool = False
-    encoder_nonlinearity: Optional[str] = None
-    encoder_proj_size: Optional[int] = None
-    # Decoder:
-    decoder_rnn_type: str = "LSTM"
-    decoder_hidden_size: int = 100
-    decoder_num_layers: int = 1
-    decoder_bias: bool = True
-    decoder_dropout: float = 0.0
-    decoder_bidirectional: bool = False
-    decoder_nonlinearity: Optional[str] = None
-    decoder_proj_size: Optional[int] = None
-    # Adapter FF NN:
-    adapter_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [50])
-    adapter_out_activation: Optional[str] = "Tanh"
-    # Predictor FF NN:
-    predictor_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [])
-    predictor_out_activation: Optional[str] = None
-    # Misc:
-    max_len: Optional[int] = None
-    optimizer_str: str = "Adam"
-    optimizer_kwargs: Dict[str, Any] = dataclasses.field(default_factory=lambda: dict(lr=0.01, weight_decay=1e-5))
-    batch_size: int = 32
-    epochs: int = 100
-    padding_indicator: float = DEFAULT_PADDING_INDICATOR
-
-
 @plugins.register_plugin(name="seq2seq_classifier", category="prediction.temporal.classification")
 class Seq2seqClassifier(BaseTemporalClassifier):
-    ParamsDefinition = Seq2seqClassifierParams
-    params: Seq2seqClassifierParams  # type: ignore
+    ParamsDefinition = Seq2seqParams
+    params: Seq2seqParams  # type: ignore
 
     def __init__(
         self,
@@ -62,8 +26,8 @@ def __init__(
         """Seq2seq classifier.
 
         Args:
-            params:
-                Parameters and defaults as defined in :class:`Seq2seqClassifierParams`.
+            **params (Any):
+                Parameters and defaults as defined in :class:`Seq2seqParams`.
 
         Example:
             >>> import doctest; doctest.ELLIPSIS_MARKER = "[...]"  # Doctest config, ignore.
@@ -136,7 +100,7 @@ def _predict_proba(
         raise NotImplementedError("Not currently supported")
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/prediction/temporal/regression/__init__.py b/src/tempor/methods/prediction/temporal/regression/__init__.py
index a812f74c..f8f6271e 100644
--- a/src/tempor/methods/prediction/temporal/regression/__init__.py
+++ b/src/tempor/methods/prediction/temporal/regression/__init__.py
@@ -1,3 +1,5 @@
+"""Temporal regression estimators."""
+
 import abc
 from typing import Any
 
@@ -10,6 +12,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check that the passed data is a temporal prediction dataset.
+
+    Args:
+        data (Any): The data to check.
+
+    Raises:
+        TypeError: If the data is not a temporal prediction dataset.
+    """
     if not isinstance(data, dataset.TemporalPredictionDataset):
         raise TypeError(
             "Expected `data` passed to a temporal regression estimator to be "
@@ -19,9 +29,15 @@ def check_data_class(data: Any) -> None:
 
 class BaseTemporalRegressor(methods_core.BasePredictor):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+        """Base class for temporal regression estimators.
+
+        Args:
+            **params (Any):
+                Parameters and defaults as defined in :class:`BasePredictorParams`.
+        """
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -34,7 +50,7 @@ def predict(  # type: ignore [override] # pylint: disable=arguments-differ
         *args: Any,
         time_delta: int = 1,
         **kwargs: Any,
-    ) -> samples.TimeSeriesSamples:
+    ) -> samples.TimeSeriesSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, n_future_steps, *args, time_delta=time_delta, **kwargs)
 
diff --git a/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py b/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
index 6d0ed1d4..93f4ad8a 100644
--- a/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
+++ b/src/tempor/methods/prediction/temporal/regression/plugin_seq2seq_regressor.py
@@ -1,59 +1,23 @@
-import dataclasses
-from typing import Any, Dict, List, Optional, cast
+"""Temporal classification estimator based on Seq2Seq model."""
+
+from typing import Any, List, cast
 
 import clairvoyance2.data.dataformat as cl_dataformat
-from clairvoyance2.data import DEFAULT_PADDING_INDICATOR
 from clairvoyance2.prediction.seq2seq import Seq2SeqRegressor, TimeIndexHorizon
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.constants import Seq2seqParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.prediction.temporal.regression import BaseTemporalRegressor
 
 
-@dataclasses.dataclass
-class Seq2seqRegressorParams:
-    # TODO: Docstrings.
-    # Encoder:
-    encoder_rnn_type: str = "LSTM"
-    encoder_hidden_size: int = 100
-    encoder_num_layers: int = 1
-    encoder_bias: bool = True
-    encoder_dropout: float = 0.0
-    encoder_bidirectional: bool = False
-    encoder_nonlinearity: Optional[str] = None
-    encoder_proj_size: Optional[int] = None
-    # Decoder:
-    decoder_rnn_type: str = "LSTM"
-    decoder_hidden_size: int = 100
-    decoder_num_layers: int = 1
-    decoder_bias: bool = True
-    decoder_dropout: float = 0.0
-    decoder_bidirectional: bool = False
-    decoder_nonlinearity: Optional[str] = None
-    decoder_proj_size: Optional[int] = None
-    # Adapter FF NN:
-    adapter_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [50])
-    adapter_out_activation: Optional[str] = "Tanh"
-    # Predictor FF NN:
-    predictor_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [])
-    predictor_out_activation: Optional[str] = None
-    # Misc:
-    max_len: Optional[int] = None
-    optimizer_str: str = "Adam"
-    optimizer_kwargs: Dict[str, Any] = dataclasses.field(default_factory=lambda: dict(lr=0.01, weight_decay=1e-5))
-    batch_size: int = 32
-    epochs: int = 100
-    padding_indicator: float = DEFAULT_PADDING_INDICATOR
-
-
 @plugins.register_plugin(name="seq2seq_regressor", category="prediction.temporal.regression")
 class Seq2seqRegressor(BaseTemporalRegressor):
-    ParamsDefinition = Seq2seqRegressorParams
-    params: Seq2seqRegressorParams  # type: ignore
+    ParamsDefinition = Seq2seqParams
+    params: Seq2seqParams  # type: ignore
 
     def __init__(
         self,
@@ -62,8 +26,8 @@ def __init__(
         """Seq2seq regressor.
 
         Args:
-            params:
-                Parameters and defaults as defined in :class:`Seq2seqClassifierParams`.
+            **params (Any):
+                Parameters and defaults as defined in :class:`Seq2seqParams`.
 
         Example:
             >>> import doctest; doctest.ELLIPSIS_MARKER = "[...]"  # Doctest config, ignore.
@@ -128,7 +92,7 @@ def _predict(  # type: ignore[override]
         return samples.TimeSeriesSamples.from_dataframe(preds)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/preprocessing/__init__.py b/src/tempor/methods/preprocessing/__init__.py
index 32637c2c..a6024441 100644
--- a/src/tempor/methods/preprocessing/__init__.py
+++ b/src/tempor/methods/preprocessing/__init__.py
@@ -1,3 +1,5 @@
+"""Preprocessing methods."""
+
 from . import encoding, imputation, nop, scaling
 
 __all__ = [
diff --git a/src/tempor/methods/preprocessing/encoding/__init__.py b/src/tempor/methods/preprocessing/encoding/__init__.py
index be749658..b6c2c712 100644
--- a/src/tempor/methods/preprocessing/encoding/__init__.py
+++ b/src/tempor/methods/preprocessing/encoding/__init__.py
@@ -1,5 +1,4 @@
-"""A module that contains methods for encoding data features, such as one-hot encoding.
-"""
+"""A module that contains methods for encoding data features, such as one-hot encoding."""
 
 from . import static, temporal
 from ._base import BaseEncoder
diff --git a/src/tempor/methods/preprocessing/encoding/_base.py b/src/tempor/methods/preprocessing/encoding/_base.py
index 28314101..2358b9e8 100644
--- a/src/tempor/methods/preprocessing/encoding/_base.py
+++ b/src/tempor/methods/preprocessing/encoding/_base.py
@@ -4,5 +4,5 @@
 
 
 class BaseEncoder(methods_core.BaseTransformer):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/encoding/static/__init__.py b/src/tempor/methods/preprocessing/encoding/static/__init__.py
index ff4af9a5..cb3cb608 100644
--- a/src/tempor/methods/preprocessing/encoding/static/__init__.py
+++ b/src/tempor/methods/preprocessing/encoding/static/__init__.py
@@ -1,3 +1,5 @@
+"""Encoding methods for static data."""
+
 from tempor.core import plugins
 
 from .._base import BaseEncoder
diff --git a/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py b/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
index 57106405..6e545377 100644
--- a/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
+++ b/src/tempor/methods/preprocessing/encoding/static/plugin_static_onehot_encoder.py
@@ -1,3 +1,5 @@
+"""One-hot encoding for static data."""
+
 import dataclasses
 from typing import Any, Callable, Dict, List, Optional, Type, Union
 
@@ -11,8 +13,7 @@
 from tempor.data import dataset
 from tempor.data.data_typing import FeatureIndex
 from tempor.data.samples import StaticSamples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, Params
 from tempor.methods.preprocessing.encoding._base import BaseEncoder
 
 # TODO: Handle SklearnArrayLike rather than just list, requires dropping OmegaConf stuff.
@@ -61,7 +62,7 @@ def __init__(self, **params: Any) -> None:
         Specify ``features`` list to encode only a subset of the features.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`StaticOneHotEncoderParams`.
 
         Example:
@@ -147,7 +148,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             CategoricalParams("drop", ["first", "if_binary"]),
             CategoricalParams("handle_unknown", ["error", "ignore", "infrequent_if_exist"]),
diff --git a/src/tempor/methods/preprocessing/encoding/temporal/__init__.py b/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
index 52753807..4e67d6cc 100644
--- a/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
+++ b/src/tempor/methods/preprocessing/encoding/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Encoding methods for temporal data."""
+
 from tempor.core import plugins
 
 from .._base import BaseEncoder
diff --git a/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py b/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
index 3e1a378a..1e1b42f7 100644
--- a/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
+++ b/src/tempor/methods/preprocessing/encoding/temporal/plugin_ts_onehot_encoder.py
@@ -1,3 +1,5 @@
+"""One-hot encoding for temporal data."""
+
 import dataclasses
 from typing import Any, Callable, Dict, List, Optional, Type, Union
 
@@ -11,8 +13,7 @@
 from tempor.data import dataset
 from tempor.data.data_typing import FeatureIndex
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, Params
 from tempor.methods.preprocessing.encoding._base import BaseEncoder
 
 # TODO: Factor out code for applying sklearn transformer to arbitrary subset of columns.
@@ -59,7 +60,7 @@ def __init__(self, **params: Any) -> None:
         Specify ``features`` list to encode only a subset of the features.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`TimeSeriesOneHotEncoderParams`.
 
         Example:
@@ -139,7 +140,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             CategoricalParams("drop", ["first", "if_binary"]),
             CategoricalParams("handle_unknown", ["error", "ignore", "infrequent_if_exist"]),
diff --git a/src/tempor/methods/preprocessing/imputation/__init__.py b/src/tempor/methods/preprocessing/imputation/__init__.py
index 675e6710..f1d8921d 100644
--- a/src/tempor/methods/preprocessing/imputation/__init__.py
+++ b/src/tempor/methods/preprocessing/imputation/__init__.py
@@ -1,3 +1,5 @@
+"""Imputation methods."""
+
 from . import static, temporal
 from ._base import BaseImputer, TabularImputerType
 
diff --git a/src/tempor/methods/preprocessing/imputation/_base.py b/src/tempor/methods/preprocessing/imputation/_base.py
index 0a02130a..128074a0 100644
--- a/src/tempor/methods/preprocessing/imputation/_base.py
+++ b/src/tempor/methods/preprocessing/imputation/_base.py
@@ -24,5 +24,5 @@
 
 
 class BaseImputer(methods_core.BaseTransformer):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py b/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
index e73aa7fb..f751d1fe 100644
--- a/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
+++ b/src/tempor/methods/preprocessing/imputation/hyperimpute_utils.py
@@ -1,3 +1,5 @@
+"""Utilities for `hyperimpute`."""
+
 import contextlib
 from typing import Generator
 
diff --git a/src/tempor/methods/preprocessing/imputation/static/__init__.py b/src/tempor/methods/preprocessing/imputation/static/__init__.py
index db33127c..10fba29b 100644
--- a/src/tempor/methods/preprocessing/imputation/static/__init__.py
+++ b/src/tempor/methods/preprocessing/imputation/static/__init__.py
@@ -1,3 +1,5 @@
+"""Imputation methods for static data."""
+
 from tempor.core import plugins
 
 from .._base import BaseImputer
diff --git a/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py b/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
index 7cafa2a9..43184e80 100644
--- a/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
+++ b/src/tempor/methods/preprocessing/imputation/static/plugin_static_tabular_imputer.py
@@ -1,3 +1,5 @@
+"""Impute static covariates using any tabular imputer from the `hyperimpute` library."""
+
 import dataclasses
 from typing import Any, Dict, List
 
@@ -6,7 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import StaticSamples
-from tempor.methods.core._params import CategoricalParams, Params
+from tempor.methods.core.params import CategoricalParams, Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer, TabularImputerType
 
 from ..hyperimpute_utils import monkeypatch_hyperimpute_logger
@@ -36,7 +38,7 @@ def __init__(self, **params: Any) -> None:
         """Impute the static covariates using any tabular imputer from the `hyperimpute` library.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`StaticTabularImputerParams`.
 
         Example:
@@ -84,7 +86,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         hs: List[Params] = [
             CategoricalParams(name="imputer", choices=list(get_args(TabularImputerType))),
         ]
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/__init__.py b/src/tempor/methods/preprocessing/imputation/temporal/__init__.py
index 183a70ce..0a6d5182 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/__init__.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Imputation methods for temporal data.""" ""
+
 from tempor.core import plugins
 
 from .._base import BaseImputer
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
index e2483ccf..3ddc5619 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_bfill.py
@@ -1,3 +1,5 @@
+"""Implementation using ``bfill``."""
+
 from typing import Any, List
 
 from typing_extensions import Self
@@ -5,7 +7,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer
 
 
@@ -14,6 +16,9 @@ class BFillImputer(BaseImputer):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         """Backward-first Time-Series Imputation.
 
+        Args:
+            **params (Any): Additional parameters.
+
         Note:
             The data will be represented as a multi-index `(sample_idx, time_idx)` dataframe of features.
             Then ``bfill``, ``ffill`` and ``fillna(0.0)``` will be called in that order.
@@ -56,5 +61,5 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return []
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
index 81e49f86..7f08854e 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ffill.py
@@ -1,3 +1,5 @@
+"""Implementation using ``ffill``."""
+
 from typing import Any, List
 
 from typing_extensions import Self
@@ -5,7 +7,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer
 
 
@@ -14,6 +16,9 @@ class FFillImputer(BaseImputer):
     def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
         """Forward-first Time-Series Imputation.
 
+        Args:
+            **params (Any): Additional parameters.
+
         Note:
             The data will be represented as a multi-index `(sample_idx, time_idx)` dataframe of features.
             Then ``ffill``, ``bfill`` and ``fillna(0.0)``` will be called in that order.
@@ -56,5 +61,5 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return []
diff --git a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
index b3eebe4c..9637d9d5 100644
--- a/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
+++ b/src/tempor/methods/preprocessing/imputation/temporal/plugin_ts_tabular_imputer.py
@@ -1,3 +1,5 @@
+"""Impute temporal covariates using any tabular imputer from the `hyperimpute` library."""
+
 import dataclasses
 from typing import Any, Dict, List
 
@@ -6,7 +8,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core._params import CategoricalParams, Params
+from tempor.methods.core.params import CategoricalParams, Params
 from tempor.methods.preprocessing.imputation._base import BaseImputer, TabularImputerType
 
 from ..hyperimpute_utils import monkeypatch_hyperimpute_logger
@@ -40,7 +42,7 @@ def __init__(self, **params: Any) -> None:
             tabular imputer will be applied to this dataframe directly.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`TemporalTabularImputerParams`.
 
         Example:
@@ -86,7 +88,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         hs: List[Params] = [
             CategoricalParams(name="imputer", choices=list(get_args(TabularImputerType))),
         ]
diff --git a/src/tempor/methods/preprocessing/nop/__init__.py b/src/tempor/methods/preprocessing/nop/__init__.py
index db8bdf5c..65f19c61 100644
--- a/src/tempor/methods/preprocessing/nop/__init__.py
+++ b/src/tempor/methods/preprocessing/nop/__init__.py
@@ -1,3 +1,5 @@
+"""No operation transformer."""
+
 from typing import Any, List
 
 from typing_extensions import Self
@@ -5,14 +7,14 @@
 import tempor.methods.core as methods_core
 from tempor.core import plugins
 from tempor.data import dataset
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 
 plugins.register_plugin_category("preprocessing.nop", methods_core.BaseTransformer)
 
 
 @plugins.register_plugin(name="nop_transformer", category="preprocessing.nop")
 class NopTransformer(methods_core.BaseTransformer):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
 
     def _fit(
@@ -27,7 +29,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return []
 
 
diff --git a/src/tempor/methods/preprocessing/scaling/__init__.py b/src/tempor/methods/preprocessing/scaling/__init__.py
index 26fbd5ba..1ab933e5 100644
--- a/src/tempor/methods/preprocessing/scaling/__init__.py
+++ b/src/tempor/methods/preprocessing/scaling/__init__.py
@@ -1,3 +1,5 @@
+"""Scaling methods."""
+
 from . import static, temporal
 from ._base import BaseScaler
 
diff --git a/src/tempor/methods/preprocessing/scaling/_base.py b/src/tempor/methods/preprocessing/scaling/_base.py
index b1b6c40d..b1d4b2a1 100644
--- a/src/tempor/methods/preprocessing/scaling/_base.py
+++ b/src/tempor/methods/preprocessing/scaling/_base.py
@@ -4,5 +4,5 @@
 
 
 class BaseScaler(methods_core.BaseTransformer):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
diff --git a/src/tempor/methods/preprocessing/scaling/static/__init__.py b/src/tempor/methods/preprocessing/scaling/static/__init__.py
index 0276474e..b7276116 100644
--- a/src/tempor/methods/preprocessing/scaling/static/__init__.py
+++ b/src/tempor/methods/preprocessing/scaling/static/__init__.py
@@ -1,3 +1,5 @@
+"""Scaling methods for static data."""
+
 from tempor.core import plugins
 
 from .._base import BaseScaler
diff --git a/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py b/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
index ccf629bd..e0acb3e3 100644
--- a/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/static/plugin_static_minmax_scaler.py
@@ -1,3 +1,5 @@
+"""MinMax scaling for the static data."""
+
 import dataclasses
 from typing import Any, Dict, List, Tuple
 
@@ -8,8 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import StaticSamples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams
+from tempor.methods.core.params import CategoricalParams, Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -37,7 +38,7 @@ def __init__(self, **params: Any) -> None:
         each feature individually such that it is in the given range on the training set, e.g. between zero and one.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`StaticMinMaxScalerParams`.
 
         Example:
@@ -86,7 +87,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             CategoricalParams("clip", [True, False]),
         ]
diff --git a/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py b/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
index 6b3ed6d3..c735a65d 100644
--- a/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/static/plugin_static_standard_scaler.py
@@ -1,3 +1,5 @@
+"""Standard scaling for the static data."""
+
 import dataclasses
 from typing import Any, Dict, List
 
@@ -8,7 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import StaticSamples
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -33,7 +35,7 @@ def __init__(self, **params: Any) -> None:
         Standardize the static features by removing the mean and scaling to unit variance.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`StaticStandardScalerParams`.
 
         Example:
@@ -81,5 +83,5 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return []
diff --git a/src/tempor/methods/preprocessing/scaling/temporal/__init__.py b/src/tempor/methods/preprocessing/scaling/temporal/__init__.py
index 8a28314a..33d042f3 100644
--- a/src/tempor/methods/preprocessing/scaling/temporal/__init__.py
+++ b/src/tempor/methods/preprocessing/scaling/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Scaling methods for temporal data."""
+
 from tempor.core import plugins
 
 from .._base import BaseScaler
diff --git a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
index 40b78a37..5c979eca 100644
--- a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_minmax_scaler.py
@@ -1,3 +1,5 @@
+"""MinMax scaling for the temporal data."""
+
 import dataclasses
 from typing import Any, Dict, List, Tuple
 
@@ -8,8 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams
+from tempor.methods.core.params import CategoricalParams, Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -40,7 +41,7 @@ def __init__(self, **params: Any) -> None:
         and the scaling will be applied to this dataframe.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`TimeSeriesMinMaxScalerParams`.
 
         Example:
@@ -83,7 +84,7 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             CategoricalParams("clip", [True, False]),
         ]
diff --git a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
index 05ef00c2..cc5b12e7 100644
--- a/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
+++ b/src/tempor/methods/preprocessing/scaling/temporal/plugin_ts_standard_scaler.py
@@ -1,3 +1,5 @@
+"""Standard scaling for the temporal data."""
+
 import dataclasses
 from typing import Any, Dict, List
 
@@ -8,7 +10,7 @@
 from tempor.core import plugins
 from tempor.data import dataset
 from tempor.data.samples import TimeSeriesSamples
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.preprocessing.scaling._base import BaseScaler
 
 
@@ -35,7 +37,7 @@ def __init__(self, **params: Any) -> None:
         applied to this dataframe.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`TimeSeriesStandardScalerParams`.
 
         Example:
@@ -77,5 +79,5 @@ def _transform(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> da
         return data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return []
diff --git a/src/tempor/methods/time_to_event/__init__.py b/src/tempor/methods/time_to_event/__init__.py
index 81a5d21d..cc8f32b8 100644
--- a/src/tempor/methods/time_to_event/__init__.py
+++ b/src/tempor/methods/time_to_event/__init__.py
@@ -1,3 +1,5 @@
+"""Time-to-event (survival) analysis methods."""
+
 import abc
 
 import pydantic
@@ -10,6 +12,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check that the passed data is of the correct class (`dataset.TimeToEventAnalysisDataset`).
+
+    Args:
+        data (Any): Data to check.
+
+    Raises:
+        TypeError: If the data is not of the correct class.
+    """
     if not isinstance(data, dataset.TimeToEventAnalysisDataset):
         raise TypeError(
             "Expected `data` passed to a survival analysis estimator to be "
@@ -18,10 +28,10 @@ def check_data_class(data: Any) -> None:
 
 
 class BaseTimeToEventAnalysis(methods_core.BasePredictor):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -37,11 +47,24 @@ def predict(  # type: ignore [no-untyped-def, override] # pylint: disable=argume
         horizons: data_typing.TimeIndex,
         *args,
         **kwargs,
-    ) -> samples.TimeSeriesSamples:  # Output is risk scores at time points, hence `samples.TimeSeriesSamples`.
+    ) -> samples.TimeSeriesSamples:
+        """Predict risk scores for the given data. Output is risk scores at time points, hence
+        `samples.TimeSeriesSamples`.
+
+        Args:
+            data (dataset.PredictiveDataset): Dataset to predict on. Should be `dataset.TimeToEventAnalysisDataset`.
+            horizons (data_typing.TimeIndex): Time points to predict at.
+            *args: Additional arguments.
+            **kwargs: Additional keyword arguments.
+
+        Returns:
+            samples.TimeSeriesSamples: Predicted risk scores at the given time points.
+        """
         check_data_class(data)
         return super().predict(data, horizons, *args, **kwargs)
 
     def predict_proba(self, data: dataset.PredictiveDataset, *args: Any, **kwargs: Any) -> Any:
+        """Not used for this case. Raises an error."""
         raise tempor.exc.UnsupportedSetupException(
             "`predict_proba` method is not supported in the time-to-event analysis setting"
         )
diff --git a/src/tempor/methods/time_to_event/helper_embedding.py b/src/tempor/methods/time_to_event/helper_embedding.py
index 1dcd7dad..9f6e8444 100644
--- a/src/tempor/methods/time_to_event/helper_embedding.py
+++ b/src/tempor/methods/time_to_event/helper_embedding.py
@@ -1,3 +1,5 @@
+"""Helper class for embedding time-series data for time-to-event analysis using Dynamic DeepHit embeddings."""
+
 import abc
 from typing import TYPE_CHECKING, Any, List, Optional, Tuple, cast
 
@@ -7,20 +9,40 @@
 
 import tempor.exc
 from tempor.data import data_typing, dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.models import utils
 from tempor.models.ddh import DynamicDeepHitModel, output_modes, rnn_modes
 
 
 class OutputTimeToEventAnalysis:
+    """Helper base class for time-to-event analysis models."""
+
     @abc.abstractmethod
     def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:  # pragma: no cover
-        ...
+        """Fit the model.
+
+        Args:
+            X (pd.DataFrame): Input covariates of fixed shape - time-series data should be embedded first.
+            T (pd.Series): Event times.
+            Y (pd.Series): Event values.
+
+        Returns:
+            Self: Fitted model.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
     @abc.abstractmethod
     def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:  # pragma: no cover
-        ...
+        """Predict risk scores.
+
+        Args:
+            X (pd.DataFrame): Input covariates of fixed shape - time-series data should be embedded first.
+            time_horizons (List): Time horizons to predict risk at.
+
+        Returns:
+            pd.DataFrame: Risk scores.
+        """
+        ...  # pylint: disable=unnecessary-ellipsis
 
 
 class DDHEmbedding:
@@ -92,6 +114,14 @@ def prepare_fit(
         self,
         data: dataset.BaseDataset,
     ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+        """Prepare data for fitting.
+
+        Args:
+            data (dataset.BaseDataset): Input dataset.
+
+        Returns:
+            Tuple[np.ndarray, np.ndarray, np.ndarray]: Processed covariate data, event times, event values.
+        """
         utils.enable_reproducibility(self.emb_model.random_state)
 
         data = cast(dataset.TimeToEventAnalysisDataset, data)
@@ -102,13 +132,24 @@ def prepare_fit(
             assert event_times is not None and event_values is not None  # nosec B101
         return processed_data, event_times, event_values
 
-    def prepare_predict(
+    def prepare_predict(  # pylint: disable=unused-argument
         self,
         data: dataset.PredictiveDataset,
         horizons: data_typing.TimeIndex,
-        *args: Any,  # pylint: disable=unused-argument
+        *args: Any,
         **kwargs: Any,
     ) -> np.ndarray:
+        """Prepare data for prediction.
+
+        Args:
+            data (dataset.PredictiveDataset): Input dataset.
+            horizons (data_typing.TimeIndex): Time horizons to predict risk at.
+            *args (Any): Additional arguments.
+            **kwargs (Any): Additional keyword arguments.
+
+        Returns:
+            np.ndarray: Processed data.
+        """
         data = cast(dataset.TimeToEventAnalysisDataset, data)
         self._validate_data(data)
         (static, temporal, observation_times, _, _) = self._convert_data(data)
@@ -116,7 +157,10 @@ def prepare_predict(
         return processed_data
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # pylint: disable=unused-argument
+    def hyperparameter_space(  # pylint: disable=unused-argument
+        *args: Any,
+        **kwargs: Any,
+    ) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="n_units_hidden", low=10, high=100, step=10),
             IntegerParams(name="n_layers_hidden", low=1, high=4),
@@ -149,12 +193,22 @@ def __init__(
         DDHEmbedding.__init__(self, emb_model=emb_model)
         self.output_model = output_model
 
-    def fit(
+    def fit(  # pylint: disable=unused-argument
         self,
         data: dataset.BaseDataset,
-        *args: Any,  # pylint: disable=unused-argument
+        *args: Any,
         **kwargs: Any,
     ) -> Self:
+        """Fit the model.
+
+        Args:
+            data (dataset.BaseDataset): Input dataset.
+            *args (Any): Additional arguments.
+            **kwargs (Any): Additional keyword arguments.
+
+        Returns:
+            Self: Fitted model.
+        """
         processed_data, event_times, event_values = self.prepare_fit(data)
 
         self.emb_model.fit(processed_data, event_times, event_values)
@@ -174,8 +228,20 @@ def predict(
         *args: Any,
         **kwargs: Any,
     ) -> samples.TimeSeriesSamples:
-        # NOTE: kwargs will be passed to DynamicDeepHitModel.predict_emb().
-        # E.g. `batch_size` batch size parameter can be provided this way.
+        """Predict risk scores.
+
+        ``*args`` and ``**kwargs`` will be passed to ``self.emb_model.predict_emb()``. E.g. ``batch_size`` batch size
+        parameter can be provided this way.
+
+        Args:
+            data (dataset.PredictiveDataset): Input dataset.
+            horizons (data_typing.TimeIndex): Time horizons to predict risk at.
+            *args (Any): Additional arguments. Passed to ``self.emb_model.predict_emb()``
+            **kwargs (Any): Additional keyword arguments. Passed to ``self.emb_model.predict_emb()``
+
+        Returns:
+            samples.TimeSeriesSamples: Predicted risk scores.
+        """
         processed_data = self.prepare_predict(data, horizons, *args, **kwargs)
 
         embeddings = self.emb_model.predict_emb(processed_data)
@@ -193,5 +259,5 @@ def predict(
         )
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return DDHEmbedding.hyperparameter_space(*args, **kwargs)
diff --git a/src/tempor/methods/time_to_event/plugin_ddh.py b/src/tempor/methods/time_to_event/plugin_ddh.py
index 67fca4be..55a023a5 100644
--- a/src/tempor/methods/time_to_event/plugin_ddh.py
+++ b/src/tempor/methods/time_to_event/plugin_ddh.py
@@ -1,3 +1,5 @@
+"""Dynamic DeepHit survival analysis model."""
+
 import dataclasses
 from typing import Any, List
 
@@ -5,7 +7,7 @@
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
-from tempor.methods.core import Params
+from tempor.methods.core.params import Params
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
 from tempor.models.ddh import DynamicDeepHitModel, OutputMode, RnnMode
 
@@ -62,7 +64,7 @@ def __init__(self, **params: Any) -> None:
                 - Risk prediction for time points beyond the last event time in the dataset may throw errors.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`DynamicDeepHitTimeToEventAnalysisParams`.
 
         References:
@@ -119,5 +121,5 @@ def _predict(  # type: ignore[override]
         )
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return DDHEmbedding.hyperparameter_space(*args, **kwargs)
diff --git a/src/tempor/methods/time_to_event/plugin_ts_coxph.py b/src/tempor/methods/time_to_event/plugin_ts_coxph.py
index 64b66d02..878794df 100644
--- a/src/tempor/methods/time_to_event/plugin_ts_coxph.py
+++ b/src/tempor/methods/time_to_event/plugin_ts_coxph.py
@@ -1,3 +1,5 @@
+"""CoxPH survival analysis model with Dynamic DeepHit embeddings."""
+
 import contextlib
 import dataclasses
 from typing import Any, Dict, Generator, List, Optional
@@ -11,8 +13,7 @@
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import FloatParams
+from tempor.methods.core.params import FloatParams, Params
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
 from tempor.models.ddh import DynamicDeepHitModel, OutputMode, RnnMode
 
@@ -121,13 +122,15 @@ def __init__(
                 Attach a penalty to the size of the coefficients during regression. Defaults to ``0``.
             fit_options (Optional[Dict], optional):
                 Pass kwargs for the fitting algorithm. Defaults to ``{"step_size": 0.1}``.
+            **kwargs (Any):
+                Additional keyword arguments for `lifelines.CoxPHFitter`.
         """
         if fit_options is None:
             fit_options = {"step_size": 0.1}
         self.fit_options = fit_options
         self.model = CoxPHFitter(alpha=alpha, penalizer=penalizer, **kwargs)
 
-    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:
+    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:  # noqa: D102
         self.constant_cols = drop_constant_columns(X)  # pylint: disable=attribute-defined-outside-init
         X = X.drop(columns=self.constant_cols)
 
@@ -140,7 +143,7 @@ def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:
 
         return self
 
-    def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:
+    def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:  # noqa: D102
         """Predict risk estimation."""
 
         X = X.drop(columns=self.constant_cols)
@@ -166,7 +169,6 @@ def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:
         return pd.DataFrame(np.concatenate(preds_, axis=0), columns=time_horizons, index=X.index)
 
 
-# TODO: Docstring.
 @plugins.register_plugin(name="ts_coxph", category="time_to_event")
 class CoxPHTimeToEventAnalysis(BaseTimeToEventAnalysis):
     ParamsDefinition = CoxPHTimeToEventAnalysisParams
@@ -176,7 +178,7 @@ def __init__(self, **params: Any) -> None:
         """CoxPH survival analysis model.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`CoxPHTimeToEventAnalysisParams`.
         """
         super().__init__(**params)
@@ -225,7 +227,7 @@ def _predict(  # type: ignore[override]
         return self.model.predict(data, horizons, *args, **kwargs)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             FloatParams(name="coxph_alpha", low=0.05, high=0.1),
             FloatParams(name="coxph_penalizer", low=0, high=0.2),
diff --git a/src/tempor/methods/time_to_event/plugin_ts_xgb.py b/src/tempor/methods/time_to_event/plugin_ts_xgb.py
index a5d09554..2d67d7c9 100644
--- a/src/tempor/methods/time_to_event/plugin_ts_xgb.py
+++ b/src/tempor/methods/time_to_event/plugin_ts_xgb.py
@@ -1,3 +1,5 @@
+"""XGB survival analysis model with Dynamic DeepHit embeddings."""
+
 import contextlib
 import dataclasses
 from typing import Any, Generator, List
@@ -12,10 +14,8 @@
 
 from tempor.core import plugins
 from tempor.data import data_typing, dataset, samples
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.time_to_event import BaseTimeToEventAnalysis
-from tempor.models.constants import DEVICE
 from tempor.models.ddh import DynamicDeepHitModel, OutputMode, RnnMode
 
 from .helper_embedding import DDHEmbeddingTimeToEventAnalysis, OutputTimeToEventAnalysis
@@ -24,9 +24,6 @@
 XGBStrategy = Literal["weibull", "debiased_bce", "km"]
 
 
-# TODO: Docstrings.
-
-
 @contextlib.contextmanager
 def monkeypatch_xgbse_xgboost2_compatibility() -> Generator:
     """There is a bug that occurs in `xgbse` with `xgboost` 2.0+.
@@ -170,6 +167,8 @@ def __init__(
         subsample: float = 0.5,
         learning_rate: float = 5e-2,
         min_child_weight: int = 50,
+        reg_lambda: float = 1.0,
+        reg_alpha: float = 0.0,
         tree_method: str = "hist",
         booster: int = 0,
         random_state: int = 0,
@@ -177,11 +176,56 @@ def __init__(
         strategy: XGBStrategy = "debiased_bce",
         bce_n_iter: int = 1000,
         time_points: int = 100,
-        reg_lambda: float = 1,
-        reg_alpha: float = 0,
-        device: Any = DEVICE,  # pylint: disable=unused-argument
         **kwargs: Any,
     ) -> None:
+        """XGB survival analysis model.
+
+        Args:
+            n_estimators (int, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``100``.
+            colsample_bynode (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``1``.
+            colsample_bylevel (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``1``.
+            colsample_bytree (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``1``.
+            max_depth (int, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``5``.
+            subsample (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``0.5``.
+            learning_rate (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``5e-2``.
+            min_child_weight (int, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``50``.
+            reg_lambda (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``1``.
+            reg_alpha (float, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``0``.
+            tree_method (str, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``"hist"``.
+            booster (int, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model as
+                ``XGBSurvivalAnalysis.booster[booster]``. Defaults to ``0``.
+            random_state (int, optional):
+                Passed as the corresponding parameter in ``xgb_params`` to the `xgbse` model. Defaults to ``0``.
+            objective (XGBObjective, optional):
+                ``"aft"`` or ``"cox"``. Chooses whether to use ``survival:aft`` or ``survival:cox`` as `xgbse` model's
+                ``"objective"`` parameter. Each case has some corresponding default parameters, see source code.
+                Defaults to ``"aft"``.
+            strategy (XGBStrategy, optional):
+                One of ``"debiased_bce"``, ``"weibull"``, or ``"km"``, chooses the corresponding `xgbse` model.
+                Defaults to ``"debiased_bce"``.
+            bce_n_iter (int, optional):
+                Passed as the ``"max_iter"`` parameter in ``lr_params`` of the `XGBSEDebiasedBCE` model, only relevant
+                to the ``"debiased_bce"`` case. Defaults to ``1000``.
+            time_points (int, optional):
+                Number of timepoints for time binning. Defaults to ``100``.
+            **kwargs (Any):
+                Additional parameters to be passed to the `xgbse` model (in ``xgb_params``).
+
+        Raises:
+            ValueError: _description_
+        """
         super().__init__()
 
         surv_params = {}
@@ -234,7 +278,7 @@ def __init__(
 
         self.time_points = time_points
 
-    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:
+    def fit(self, X: pd.DataFrame, T: pd.Series, Y: pd.Series) -> Self:  # noqa: D102
         y = convert_to_structured(T, Y)
 
         censored_times = T[Y == 0]
@@ -256,8 +300,7 @@ def _find_nearest(self, array: np.ndarray, value: float) -> float:
         idx = (np.abs(array - value)).argmin()
         return array[idx]
 
-    def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:
-        """Predict risk."""
+    def predict_risk(self, X: pd.DataFrame, time_horizons: List) -> pd.DataFrame:  # noqa: D102
         chunks = int(len(X) / 1024) + 1
 
         preds_ = []
@@ -283,7 +326,7 @@ def __init__(self, **params: Any) -> None:
         """XGB survival analysis model.
 
         Args:
-            params:
+            **params (Any):
                 Parameters and defaults as defined in :class:`XGBTimeToEventAnalysisParams`.
         """
         super().__init__(**params)
@@ -348,7 +391,7 @@ def _predict(  # type: ignore[override]
         return self.model.predict(data, horizons, *args, **kwargs)
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="xgb_max_depth", low=2, high=6),
             IntegerParams(name="xgb_min_child_weight", low=0, high=50),
diff --git a/src/tempor/methods/treatments/__init__.py b/src/tempor/methods/treatments/__init__.py
index eef95d67..936a1ab4 100644
--- a/src/tempor/methods/treatments/__init__.py
+++ b/src/tempor/methods/treatments/__init__.py
@@ -1,3 +1,5 @@
+"""Treatment effects methods."""
+
 from . import one_off, temporal
 
 __all__ = [
diff --git a/src/tempor/methods/treatments/one_off/__init__.py b/src/tempor/methods/treatments/one_off/__init__.py
index 6abad0bb..0f44d403 100644
--- a/src/tempor/methods/treatments/one_off/__init__.py
+++ b/src/tempor/methods/treatments/one_off/__init__.py
@@ -1,3 +1,5 @@
+"""One-off treatment effect estimation methods, that is, the treatment is a one-off event."""
+
 from . import classification, regression
 from ._base import BaseOneOffTreatmentEffects
 
diff --git a/src/tempor/methods/treatments/one_off/_base.py b/src/tempor/methods/treatments/one_off/_base.py
index 3a4ec861..221efdd0 100644
--- a/src/tempor/methods/treatments/one_off/_base.py
+++ b/src/tempor/methods/treatments/one_off/_base.py
@@ -10,6 +10,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check that the passed data is of the correct class (`dataset.OneOffTreatmentEffectsDataset`).
+
+    Args:
+        data (Any): Data to check.
+
+    Raises:
+        TypeError: If the data is not of the correct class.
+    """
     if not isinstance(data, dataset.OneOffTreatmentEffectsDataset):
         raise TypeError(
             "Expected `data` passed to a one-off treatment effects estimator to be "
@@ -18,10 +26,10 @@ def check_data_class(data: Any) -> None:
 
 
 class BaseOneOffTreatmentEffects(methods_core.BasePredictor):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -32,7 +40,7 @@ def predict(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> samples.StaticSamples:
+    ) -> samples.StaticSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
@@ -49,6 +57,16 @@ def predict_counterfactuals(
         *args: Any,
         **kwargs: Any,
     ) -> List:
+        """Predict counterfactuals for the given data.
+
+        Args:
+            data (dataset.PredictiveDataset): Input data.
+            *args (Any): Additional arguments.
+            **kwargs (Any): Additional keyword arguments.
+
+        Returns:
+            List: List of counterfactual predictions.
+        """
         check_data_class(data)
         return super().predict_counterfactuals(data, *args, **kwargs)
 
diff --git a/src/tempor/methods/treatments/one_off/classification/__init__.py b/src/tempor/methods/treatments/one_off/classification/__init__.py
index a02a9c95..c60253bc 100644
--- a/src/tempor/methods/treatments/one_off/classification/__init__.py
+++ b/src/tempor/methods/treatments/one_off/classification/__init__.py
@@ -1,3 +1,5 @@
+"""One-off treatment effect estimation methods, with classification targets."""
+
 # NOTE: Uncomment once have at least one treatments.one_off.classification plugin.
 
 # from tempor.core import plugins
diff --git a/src/tempor/methods/treatments/one_off/regression/__init__.py b/src/tempor/methods/treatments/one_off/regression/__init__.py
index f0191050..d0a2bfcf 100644
--- a/src/tempor/methods/treatments/one_off/regression/__init__.py
+++ b/src/tempor/methods/treatments/one_off/regression/__init__.py
@@ -1,3 +1,5 @@
+"""One-off treatment effect estimation methods, with regression targets."""
+
 from tempor.core import plugins
 from tempor.methods.treatments.one_off._base import BaseOneOffTreatmentEffects
 
diff --git a/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py b/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
index b3089545..852712dc 100644
--- a/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
+++ b/src/tempor/methods/treatments/one_off/regression/plugin_synctwin_regressor.py
@@ -1,3 +1,5 @@
+"""SyncTwin treatment effects estimation."""
+
 import dataclasses
 from typing import Any, List
 
@@ -6,13 +8,14 @@
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import tempor_dataset_to_clairvoyance2_dataset
-from tempor.methods.core import Params
-from tempor.methods.core._params import FloatParams, IntegerParams
+from tempor.methods.core.params import FloatParams, IntegerParams, Params
 from tempor.methods.treatments.one_off._base import BaseOneOffTreatmentEffects
 
 
 @dataclasses.dataclass
 class SyncTwinParams:
+    """Parameters for SyncTwin model. See paper "SyncTwin: Treatment Effect Estimation with Longitudinal Outcomes"."""
+
     # Main hyperparameters:
     hidden_size: int = 20
     tau: float = 1.0
@@ -38,6 +41,9 @@ def __init__(
     ) -> None:
         """SyncTwin treatment effects estimation.
 
+        Args:
+            **params (Any): Parameters for the model.
+
         Example:
             >>> from tempor import plugin_loader
             >>>
@@ -129,7 +135,7 @@ def _predict_counterfactuals(
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="hidden_size", low=10, high=500),
             FloatParams(name="tau", low=0.0, high=2.0),
diff --git a/src/tempor/methods/treatments/temporal/__init__.py b/src/tempor/methods/treatments/temporal/__init__.py
index 0e11e43b..659e3f31 100644
--- a/src/tempor/methods/treatments/temporal/__init__.py
+++ b/src/tempor/methods/treatments/temporal/__init__.py
@@ -1,3 +1,5 @@
+"""Temporal treatment effect estimation methods, that is, the treatment is a time-series."""
+
 from . import classification, regression
 from ._base import BaseTemporalTreatmentEffects
 
diff --git a/src/tempor/methods/treatments/temporal/_base.py b/src/tempor/methods/treatments/temporal/_base.py
index e6711a9a..87c66f07 100644
--- a/src/tempor/methods/treatments/temporal/_base.py
+++ b/src/tempor/methods/treatments/temporal/_base.py
@@ -10,6 +10,14 @@
 
 
 def check_data_class(data: Any) -> None:
+    """Check that the passed data is of the correct class (`dataset.OneOffTreatmentEffectsDataset`).
+
+    Args:
+        data (Any): Data to check.
+
+    Raises:
+        TypeError: If the data is not of the correct class.
+    """
     if not isinstance(data, dataset.TemporalTreatmentEffectsDataset):
         raise TypeError(
             "Expected `data` passed to a temporal treatment effects estimator to be "
@@ -18,10 +26,10 @@ def check_data_class(data: Any) -> None:
 
 
 class BaseTemporalTreatmentEffects(methods_core.BasePredictor):
-    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation
+    def __init__(self, **params: Any) -> None:  # pylint: disable=useless-super-delegation  # noqa: D107
         super().__init__(**params)
 
-    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:
+    def fit(self, data: dataset.BaseDataset, *args: Any, **kwargs: Any) -> Self:  # noqa: D102
         check_data_class(data)
         super().fit(data, *args, **kwargs)
         return self
@@ -32,7 +40,7 @@ def predict(
         data: dataset.PredictiveDataset,
         *args: Any,
         **kwargs: Any,
-    ) -> samples.TimeSeriesSamples:
+    ) -> samples.TimeSeriesSamples:  # noqa: D102
         check_data_class(data)
         return super().predict(data, *args, **kwargs)
 
@@ -49,6 +57,16 @@ def predict_counterfactuals(
         *args: Any,
         **kwargs: Any,
     ) -> List:
+        """Predict counterfactuals for the given data.
+
+        Args:
+            data (dataset.PredictiveDataset): Data to predict counterfactuals for.
+            *args (Any): Additional positional arguments.
+            **kwargs (Any): Additional keyword arguments.
+
+        Returns:
+            List: List of counterfactual predictions.
+        """
         check_data_class(data)
         return super().predict_counterfactuals(data, *args, **kwargs)
 
diff --git a/src/tempor/methods/treatments/temporal/classification/__init__.py b/src/tempor/methods/treatments/temporal/classification/__init__.py
index ceaa26cf..a102e7fb 100644
--- a/src/tempor/methods/treatments/temporal/classification/__init__.py
+++ b/src/tempor/methods/treatments/temporal/classification/__init__.py
@@ -1,3 +1,5 @@
+"""One-off treatment effect estimation methods, with classification targets."""
+
 from tempor.core import plugins
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
diff --git a/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py b/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
index 9eb6e02b..a4f7b28b 100644
--- a/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
+++ b/src/tempor/methods/treatments/temporal/classification/plugin_crn_classifier.py
@@ -1,59 +1,24 @@
-import dataclasses
-from typing import Any, Dict, List, Optional, cast
+"""Counterfactual Recurrent Network treatment effects model for classification on the outcomes (targets)."""
+
+from typing import Any, List, Optional, cast
 
 import clairvoyance2.data.dataformat as cl_dataformat
 import pandas as pd
-from clairvoyance2.data import DEFAULT_PADDING_INDICATOR
 from clairvoyance2.treatment_effects.crn import CRNClassifier, TimeIndexHorizon
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.constants import Seq2seqParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
 
-@dataclasses.dataclass
-class CRNParams:
-    # Encoder:
-    encoder_rnn_type: str = "LSTM"
-    encoder_hidden_size: int = 100
-    encoder_num_layers: int = 1
-    encoder_bias: bool = True
-    encoder_dropout: float = 0.0
-    encoder_bidirectional: bool = False
-    encoder_nonlinearity: Optional[str] = None
-    encoder_proj_size: Optional[int] = None
-    # Decoder:
-    decoder_rnn_type: str = "LSTM"
-    decoder_hidden_size: int = 100
-    decoder_num_layers: int = 1
-    decoder_bias: bool = True
-    decoder_dropout: float = 0.0
-    decoder_bidirectional: bool = False
-    decoder_nonlinearity: Optional[str] = None
-    decoder_proj_size: Optional[int] = None
-    # Adapter FF NN:
-    adapter_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [50])
-    adapter_out_activation: Optional[str] = "Tanh"
-    # Predictor FF NN:
-    predictor_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [])
-    predictor_out_activation: Optional[str] = None
-    # Misc:
-    max_len: Optional[int] = None
-    optimizer_str: str = "Adam"
-    optimizer_kwargs: Dict[str, Any] = dataclasses.field(default_factory=lambda: dict(lr=0.01, weight_decay=1e-5))
-    batch_size: int = 32
-    epochs: int = 100
-    padding_indicator: float = DEFAULT_PADDING_INDICATOR
-
-
 @plugins.register_plugin(name="crn_classifier", category="treatments.temporal.classification")
 class CRNTreatmentsClassifier(BaseTemporalTreatmentEffects):
-    ParamsDefinition = CRNParams
-    params: CRNParams  # type: ignore
+    ParamsDefinition = Seq2seqParams
+    params: Seq2seqParams  # type: ignore
 
     def __init__(
         self,
@@ -61,6 +26,9 @@ def __init__(
     ) -> None:
         """Counterfactual Recurrent Network treatment effects model for classification on the outcomes (targets).
 
+        Args:
+            **params (Any): Parameters for the model.
+
         Example:
             >>> from tempor import plugin_loader
             >>>
@@ -165,7 +133,7 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/methods/treatments/temporal/regression/__init__.py b/src/tempor/methods/treatments/temporal/regression/__init__.py
index db04b180..4f7d89db 100644
--- a/src/tempor/methods/treatments/temporal/regression/__init__.py
+++ b/src/tempor/methods/treatments/temporal/regression/__init__.py
@@ -1,3 +1,5 @@
+"""One-off treatment effect estimation methods, with regression targets."""
+
 from tempor.core import plugins
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
diff --git a/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py b/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
index 73116a65..24eabf34 100644
--- a/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
+++ b/src/tempor/methods/treatments/temporal/regression/plugin_crn_regressor.py
@@ -1,59 +1,24 @@
-import dataclasses
-from typing import Any, Dict, List, Optional, cast
+"""Counterfactual Recurrent Network treatment effects model for regression on the outcomes (targets)."""
+
+from typing import Any, List, Optional, cast
 
 import clairvoyance2.data.dataformat as cl_dataformat
 import pandas as pd
-from clairvoyance2.data import DEFAULT_PADDING_INDICATOR
 from clairvoyance2.treatment_effects.crn import CRNRegressor, TimeIndexHorizon
 from typing_extensions import Self
 
 from tempor.core import plugins
 from tempor.data import dataset, samples
 from tempor.data.clv2conv import _from_clv2_time_series, tempor_dataset_to_clairvoyance2_dataset
-from tempor.methods.core import Params
-from tempor.methods.core._params import CategoricalParams, FloatParams, IntegerParams
+from tempor.methods.constants import Seq2seqParams
+from tempor.methods.core.params import CategoricalParams, FloatParams, IntegerParams, Params
 from tempor.methods.treatments.temporal._base import BaseTemporalTreatmentEffects
 
 
-@dataclasses.dataclass
-class CRNParams:
-    # Encoder:
-    encoder_rnn_type: str = "LSTM"
-    encoder_hidden_size: int = 100
-    encoder_num_layers: int = 1
-    encoder_bias: bool = True
-    encoder_dropout: float = 0.0
-    encoder_bidirectional: bool = False
-    encoder_nonlinearity: Optional[str] = None
-    encoder_proj_size: Optional[int] = None
-    # Decoder:
-    decoder_rnn_type: str = "LSTM"
-    decoder_hidden_size: int = 100
-    decoder_num_layers: int = 1
-    decoder_bias: bool = True
-    decoder_dropout: float = 0.0
-    decoder_bidirectional: bool = False
-    decoder_nonlinearity: Optional[str] = None
-    decoder_proj_size: Optional[int] = None
-    # Adapter FF NN:
-    adapter_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [50])
-    adapter_out_activation: Optional[str] = "Tanh"
-    # Predictor FF NN:
-    predictor_hidden_dims: List[int] = dataclasses.field(default_factory=lambda: [])
-    predictor_out_activation: Optional[str] = None
-    # Misc:
-    max_len: Optional[int] = None
-    optimizer_str: str = "Adam"
-    optimizer_kwargs: Dict[str, Any] = dataclasses.field(default_factory=lambda: dict(lr=0.01, weight_decay=1e-5))
-    batch_size: int = 32
-    epochs: int = 100
-    padding_indicator: float = DEFAULT_PADDING_INDICATOR
-
-
 @plugins.register_plugin(name="crn_regressor", category="treatments.temporal.regression")
 class CRNTreatmentsRegressor(BaseTemporalTreatmentEffects):
-    ParamsDefinition = CRNParams
-    params: CRNParams  # type: ignore
+    ParamsDefinition = Seq2seqParams
+    params: Seq2seqParams  # type: ignore
 
     def __init__(
         self,
@@ -61,6 +26,9 @@ def __init__(
     ) -> None:
         """Counterfactual Recurrent Network treatment effects model for regression on the outcomes (targets).
 
+        Args:
+            **params (Any): Parameters for the model.
+
         References:
             Estimating counterfactual treatment outcomes over time through adversarially balanced representations,
             Ioana Bica, Ahmed M. Alaa, James Jordon, Mihaela van der Schaar.
@@ -169,7 +137,7 @@ def _predict_counterfactuals(  # type: ignore[override]  # pylint: disable=argum
         return counterfactuals
 
     @staticmethod
-    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:
+    def hyperparameter_space(*args: Any, **kwargs: Any) -> List[Params]:  # noqa: D102
         return [
             IntegerParams(name="encoder_hidden_size", low=10, high=500),
             IntegerParams(name="encoder_num_layers", low=1, high=10),
diff --git a/src/tempor/models/__init__.py b/src/tempor/models/__init__.py
index e69de29b..3c5b64df 100644
--- a/src/tempor/models/__init__.py
+++ b/src/tempor/models/__init__.py
@@ -0,0 +1,1 @@
+"""Package directory for underlying model components used by various methods."""
diff --git a/src/tempor/models/constants.py b/src/tempor/models/constants.py
index fc81e633..dc3889b8 100644
--- a/src/tempor/models/constants.py
+++ b/src/tempor/models/constants.py
@@ -1,12 +1,19 @@
+"""Common constants for the ``models`` package directory."""
+
 import torch
 from typing_extensions import Literal
 
 DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
+"""`torch.device` to use for computation, defaults to ``"cuda:0"`` if available, else ``"cpu"``."""
 
 EPS: float = torch.finfo(torch.float32).eps
+"""Machine epsilon for ``torch.float32``."""
 
 ModelTaskType = Literal["classification", "regression"]
+"""Possible values specifying a model task type."""
+
 ODEBackend = Literal["ode", "cde", "laplace"]
+"""Possible values specifying an ODE backend."""
 
 Nonlin = Literal[
     "none",
diff --git a/src/tempor/models/ddh.py b/src/tempor/models/ddh.py
index 06e2393b..fce5bf6b 100644
--- a/src/tempor/models/ddh.py
+++ b/src/tempor/models/ddh.py
@@ -1,3 +1,5 @@
+"""Model components for the Dynamic DeepHit implementation."""
+
 import warnings
 from copy import deepcopy
 from typing import Any, List, Optional, Tuple, Union
@@ -57,10 +59,6 @@ def get_padded_features(
 
 
 class DynamicDeepHitModel:
-    """This implementation considers that the last event happen at the same time for each patient.
-    The CIF is therefore simplified.
-    """
-
     def __init__(
         self,
         split: int = 100,
@@ -81,6 +79,11 @@ def __init__(
         clipping_value: int = 1,
         output_mode: str = "MLP",
     ) -> None:
+        """Dynamic DeepHit model implementation.
+
+        This implementation considers that the last event happen at the same time for each patient.
+        The CIF is therefore simplified.
+        """
         self.split = split
         self.split_time = None
 
@@ -132,6 +135,16 @@ def fit(
         t: np.ndarray,
         e: np.ndarray,
     ) -> Self:
+        """Fit the model to the data.
+
+        Args:
+            x (np.ndarray): Covariates.
+            t (np.ndarray): Event times.
+            e (np.ndarray): Event values.
+
+        Returns:
+            Self: Trained model.
+        """
         discretized_t, self.split_time = self.discretize(t, self.split, self.split_time)
         processed_data = self._preprocess_training_data(x, discretized_t, e)
         x_train, t_train, e_train, x_val, t_val, e_val = processed_data
@@ -203,16 +216,22 @@ def fit(
 
         return self
 
-    def discretize(self, t: Union[np.ndarray, List[np.ndarray]], split: int, split_time: Optional[int] = None) -> Tuple:
+    def discretize(
+        self, t: Union[np.ndarray, List[np.ndarray]], split: int, split_time: Optional[List[float]] = None
+    ) -> Tuple:
         """Discretize the survival horizon.
 
         Args:
-            t (List of Array): Time of events
-            split (int): Number of bins
-            split_time (List, optional): List of bins (must be same length than split). Defaults to None.
+            t (Union[np.ndarray, List[np.ndarray]]):
+                Times of events.
+            split (int):
+                Number of bins.
+            split_time (Optional[List[float]], optional):
+                List of bins (must be same length than split). Provide if ``split`` is not provided.
+                Defaults to `None`.
 
         Returns:
-            List of Array: Discretized events time
+            Tuple: ``(t_discretized, split_time)`` Discretized events time; split time.
         """
         if split_time is None:
             _, split_time = np.histogram(t, split - 1)  # type: ignore
@@ -271,6 +290,14 @@ def predict_emb(
         self,
         x: np.ndarray,
     ) -> torch.Tensor:
+        """Predict the embedding of the data.
+
+        Args:
+            x (np.ndarray): Covariates.
+
+        Returns:
+            torch.Tensor: Embedding of the data.
+        """
         if self.model is None:
             raise RuntimeError(
                 "The model has not been fitted yet. Please fit the "
@@ -291,6 +318,18 @@ def predict_survival(
         all_step: bool = False,
         batch_size: int = 100,
     ) -> np.ndarray:
+        """Predict the survival function.
+
+        Args:
+            x (np.ndarray): Covariates.
+            t (List): Times to predict the survival function.
+            risk (int, optional): Risk value. Defaults to ``1``.
+            all_step (bool, optional): Predict all steps. Defaults to `False`.
+            batch_size (int, optional): Batch size. Defaults to ``100``.
+
+        Returns:
+            np.ndarray: Array of survival function values.
+        """
         if self.model is None:
             raise RuntimeError(
                 "The model has not been fitted yet. Please fit the "
@@ -330,6 +369,16 @@ def predict_survival(
         return 1 - np.asarray(output).T
 
     def predict_risk(self, x: np.ndarray, t: List, **kwargs: Any) -> np.ndarray:
+        """Predict the risk.
+
+        Args:
+            x (np.ndarray): Covariates.
+            t (List): Times to predict the risk.
+            **kwargs (Any): Additional arguments passed to ``predict_survival``.
+
+        Returns:
+            np.ndarray: Array of risk values.
+        """
         return 1 - self.predict_survival(x, t, **kwargs)
 
     def negative_log_likelihood(
@@ -408,6 +457,7 @@ def total_loss(
         t: torch.Tensor,
         e: torch.Tensor,
     ) -> torch.Tensor:
+        """Compute total loss."""
         if self.model is None:
             raise RuntimeError("Invalid model for loss")
 
@@ -441,6 +491,7 @@ def __init__(
         output_type: str = "MLP",
         device: Any = constants.DEVICE,
     ) -> None:
+        """Dynamic DeepHit layers component."""
         super(DynamicDeepHitLayers, self).__init__()
 
         self.input_dim = input_dim
@@ -548,6 +599,7 @@ def __init__(
         self.to(self.device)
 
     def forward_attention(self, x: torch.Tensor, inputmask: torch.Tensor, hidden: torch.Tensor) -> torch.Tensor:
+        """Forward attention implementation."""
         # Attention using last observation to predict weight of all previously observed
         # Extract last observation (the one used for predictions)
         last_observations = (~inputmask).sum(dim=1) - 1
diff --git a/src/tempor/models/mlp.py b/src/tempor/models/mlp.py
index 4bfbba2d..942852cc 100644
--- a/src/tempor/models/mlp.py
+++ b/src/tempor/models/mlp.py
@@ -1,3 +1,5 @@
+"""Model implementations related to MLP / fully connected neural networks."""
+
 from typing import Any, Callable, List, Optional, Tuple, Type, Union
 
 import numpy as np
@@ -20,11 +22,21 @@ def __init__(
         self,
         n_units_in: int,
         n_units_out: int,
-        dropout: float = 0,
+        dropout: float = 0.0,
         batch_norm: bool = False,
         nonlin: Optional[Nonlin] = "relu",
         device: Any = constants.DEVICE,
     ) -> None:
+        """Linear layer with optional dropout, batch norm, and nonlinearity.
+
+        Args:
+            n_units_in (int): Number of input units.
+            n_units_out (int): Number of output units.
+            dropout (float, optional): Dropout. Defaults to ``0.0``.
+            batch_norm (bool, optional): Batch normalization. Defaults to `False`.
+            nonlin (Optional[Nonlin], optional): Nonlinearity (activation function). Defaults to ``"relu"``.
+            device (Any, optional): Device. Defaults to ``constants.DEVICE``.
+        """
         super(LinearLayer, self).__init__()
 
         self.device = device
@@ -44,6 +56,7 @@ def __init__(
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         return self.model(X.float()).to(self.device)  # pylint: disable=not-callable
 
 
@@ -53,11 +66,21 @@ def __init__(
         self,
         n_units_in: int,
         n_units_out: int,
-        dropout: float = 0,
+        dropout: float = 0.0,
         batch_norm: bool = False,
         nonlin: Optional[Nonlin] = "relu",
         device: Any = constants.DEVICE,
     ) -> None:
+        """Residual layer.
+
+        Args:
+            n_units_in (int): Number of input units.
+            n_units_out (int): Number of output units.
+            dropout (float, optional): Dropout. Defaults to ``0.0``.
+            batch_norm (bool, optional): Batch normalization. Defaults to `False`.
+            nonlin (Optional[Nonlin], optional): Nonlinearity (activation function). Defaults to ``"relu"``.
+            device (Any, optional): Device. Defaults to `constants.DEVICE`.
+        """
         super(ResidualLayer, self).__init__(
             n_units_in,
             n_units_out,
@@ -71,6 +94,7 @@ def __init__(
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         if X.shape[-1] == 0:
             return torch.zeros((*X.shape[:-1], self.n_units_out)).to(self.device)
 
@@ -79,13 +103,21 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
 
 
 class MultiActivationHead(nn.Module):
-    """Final layer with multiple activations. Useful for tabular data."""
-
     def __init__(
         self,
         activations: List[Tuple[nn.Module, int]],
         device: Any = constants.DEVICE,
     ) -> None:
+        """Final layer with multiple activations. Useful for tabular data. The different activations are applied to
+        different sub-lengths of the ``X`` tensor in the forward pass, on its final dimension. Hence the ``X`` tensor
+        must have a shape of ``(..., sum(activation_lengths))``.
+
+        Args:
+            activations (List[Tuple[nn.Module, int]]):
+                List of tuples of activations and their lengths, ``[(activation, activation_length), ...]``.
+            device (Any, optional):
+                Device. Defaults to `constants.DEVICE`.
+        """
         super(MultiActivationHead, self).__init__()
         self.activations = []
         self.activation_lengths = []
@@ -97,6 +129,7 @@ def __init__(
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         if X.shape[-1] != np.sum(self.activation_lengths):
             raise RuntimeError(
                 f"Shape mismatch for the activations: expected {np.sum(self.activation_lengths)}. Got shape {X.shape}."
@@ -294,6 +327,7 @@ def __init__(
                 self.loss = nn.MSELoss()
 
     def fit(self, X: np.ndarray, y: np.ndarray) -> "MLP":
+        """Fit (train) the model."""
         Xt = self._check_tensor(X)
         yt = self._check_tensor(y)
 
@@ -303,6 +337,14 @@ def fit(self, X: np.ndarray, y: np.ndarray) -> "MLP":
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict_proba(self, X: np.ndarray) -> np.ndarray:
+        """Predict probabilities for classification tasks.
+
+        Args:
+            X (np.ndarray): Input data.
+
+        Returns:
+            np.ndarray: Predicted probabilities.
+        """
         if self.task_type != "classification":
             raise ValueError(f"Invalid task type for predict_proba {self.task_type}")
 
@@ -315,6 +357,7 @@ def predict_proba(self, X: np.ndarray) -> np.ndarray:
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def predict(self, X: np.ndarray) -> np.ndarray:
+        """Make predictions."""
         with torch.no_grad():
             Xt = self._check_tensor(X)
 
@@ -326,6 +369,7 @@ def predict(self, X: np.ndarray) -> np.ndarray:
                 return yt.cpu().numpy().squeeze()
 
     def score(self, X: np.ndarray, y: np.ndarray) -> float:
+        """Compute the default score of the model. See source code."""
         y_pred = self.predict(X)
         if self.task_type == "classification":
             return np.mean(y_pred == y)
@@ -334,6 +378,7 @@ def score(self, X: np.ndarray, y: np.ndarray) -> float:
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, X: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         return self.model(X.float())  # pylint: disable=not-callable
 
     def _train_epoch(self, loader: torch.utils.data.DataLoader) -> float:
@@ -416,4 +461,5 @@ def _check_tensor(self, X: Union[torch.Tensor, np.ndarray]) -> torch.Tensor:
             return torch.from_numpy(np.asarray(X)).to(self.device)
 
     def __len__(self) -> int:
+        """Return the number of layers in the model ``len(self.model)``."""
         return len(self.model)
diff --git a/src/tempor/models/samplers.py b/src/tempor/models/samplers.py
index 52010734..8e466fa2 100644
--- a/src/tempor/models/samplers.py
+++ b/src/tempor/models/samplers.py
@@ -1,3 +1,5 @@
+"""Custom `torch` samplers."""
+
 from typing import Any, Generator, List, Optional, Tuple
 
 import numpy as np
@@ -13,38 +15,37 @@
 class BaseSampler(torch.utils.data.sampler.Sampler):
     """DataSampler samples the conditional vector and corresponding data."""
 
-    def get_dataset_conditionals(self) -> Optional[np.ndarray]:  # pragma: no cover
+    def get_dataset_conditionals(self) -> Optional[np.ndarray]:  # pragma: no cover  # noqa: D102
         return None
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def sample_conditional(
         self, batch: int, **kwargs: Any  # pylint: disable=unused-argument
-    ) -> Optional[Tuple]:  # pragma: no cover
+    ) -> Optional[Tuple]:  # pragma: no cover  # noqa: D102
         return None
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def sample_conditional_for_class(
         self, batch: int, c: int  # pylint: disable=unused-argument
-    ) -> Optional[np.ndarray]:  # pragma: no cover
+    ) -> Optional[np.ndarray]:  # pragma: no cover  # noqa: D102
         return None
 
-    def conditional_dimension(self) -> int:  # pragma: no cover
+    def conditional_dimension(self) -> int:  # pragma: no cover  # noqa: D102
         """Return the total number of categories."""
         return 0
 
-    def conditional_probs(self) -> Optional[np.ndarray]:  # pragma: no cover
+    def conditional_probs(self) -> Optional[np.ndarray]:  # pragma: no cover  # noqa: D102
         """Return the total number of categories."""
         return None
 
-    def train_test(self) -> Tuple:  # pragma: no cover
+    def train_test(self) -> Tuple:  # pragma: no cover  # noqa: D102
         raise NotImplementedError()
 
 
 class ImbalancedDatasetSampler(BaseSampler):
-    """Samples elements randomly from a given list of indices for imbalanced dataset."""
-
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def __init__(self, labels: List, train_size: float = 0.8) -> None:
+        """Samples elements randomly from a given list of indices for imbalanced dataset."""
         super().__init__(None)
 
         # if indices is not provided, all elements in the dataset will be considered
@@ -70,14 +71,19 @@ def __init__(self, labels: List, train_size: float = 0.8) -> None:
 
         self.weights = torch.DoubleTensor(weights.to_list())
 
-    def __iter__(self) -> Generator:
+    def __iter__(self) -> Generator:  # noqa: D105
         return (
             self.train_mapping[self.train_idx[i]]
             for i in torch.multinomial(self.weights, self.num_train_samples, replacement=True)
         )
 
-    def __len__(self) -> int:
+    def __len__(self) -> int:  # noqa: D105
         return len(self.train_idx)
 
     def train_test(self) -> Tuple:
+        """Return the train and test indices.
+
+        Returns:
+            Tuple: ``(self.train_idx, self.test_idx)``
+        """
         return self.train_idx, self.test_idx
diff --git a/src/tempor/models/transformer.py b/src/tempor/models/transformer.py
index 93a1e0d7..b98a7cbb 100644
--- a/src/tempor/models/transformer.py
+++ b/src/tempor/models/transformer.py
@@ -1,3 +1,5 @@
+"""Model implementations for Transformers."""
+
 from typing import Any
 
 import torch
@@ -9,19 +11,33 @@
 
 class Permute(nn.Module):
     def __init__(self, *dims: Any) -> None:
+        """Permute dimensions of a tensor with `torch.Tensor.permute`.
+
+        Args:
+            *dims (Any):
+                Dimensions to permute.
+        """
         super().__init__()
         self.dims = dims
 
     def forward(self, x: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         return x.permute(self.dims)
 
 
 class Transpose(nn.Module):
     def __init__(self, *dims: Any, contiguous: bool = False) -> None:
+        """Transpose dimensions of a tensor with `torch.Tensor.transpose`.
+
+        Args:
+            *dims (Any): Dimensions to transpose.
+            contiguous (bool, optional): Whether to call `.contiguous()` on the output. Defaults to `False`.
+        """
         super().__init__()
         self.dims, self.contiguous = dims, contiguous
 
     def forward(self, x: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         if self.contiguous:
             return x.transpose(*self.dims).contiguous()
         else:
@@ -53,7 +69,7 @@ def __init__(
                 The dimension of the feedforward network model. Defaults to ``128``.
             dropout (float, optional):
                 Dropout value passed to `~torch.nn.modules.transformer.TransformerEncoderLayer` s. Defaults to ``0.1``.
-            activation (Nonlin, optional):
+            activation (str, optional):
                 The activation function of intermediate layer, ``"relu"`` or ``"gelu"``. Defaults to ``"relu"``.
             n_layers_hidden (int, optional):
                 The number of sub-encoder-layers in the encoder. Defaults to ``1``.
@@ -87,4 +103,5 @@ def __init__(
         ).to(device)
 
     def forward(self, x: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         return self.model(x)  # pylint: disable=not-callable
diff --git a/src/tempor/models/ts_model.py b/src/tempor/models/ts_model.py
index 8e8b9ae7..926af6e1 100644
--- a/src/tempor/models/ts_model.py
+++ b/src/tempor/models/ts_model.py
@@ -1,3 +1,5 @@
+"""Model implementations for time-series model(s)."""
+
 from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
 import numpy as np
@@ -39,6 +41,7 @@
     "OmniScaleCNN",
     "XCM",
 ]
+"""Time series model 'mode', that is, the underlying architecture."""
 
 
 class TimeSeriesModel(nn.Module):
@@ -227,6 +230,7 @@ def forward(
         temporal_data: torch.Tensor,
         observation_times: torch.Tensor,
     ) -> torch.Tensor:
+        """Forward pass."""
         # x shape (batch, time_step, input_size)
         # r_out shape (batch, time_step, output_size)
 
@@ -262,6 +266,7 @@ def predict(
         temporal_data: Union[List, np.ndarray],
         observation_times: Union[List, np.ndarray],
     ) -> np.ndarray:
+        """Make predictions."""
         self.eval()
         with torch.no_grad():
             (
@@ -294,6 +299,7 @@ def predict_proba(
         temporal_data: Union[List, np.ndarray],
         observation_times: Union[List, np.ndarray],
     ) -> np.ndarray:
+        """Predict probabilities."""
         self.eval()
         if self.task_type != "classification":
             raise RuntimeError("Task valid only for classification")
@@ -325,6 +331,7 @@ def score(
         observation_times: Union[List, np.ndarray],
         outcome: np.ndarray,
     ) -> float:
+        """Get default model score."""
         y_pred = self.predict(static_data, temporal_data, observation_times)
         if self.task_type == "classification":
             return np.mean(y_pred.astype(int) == outcome.astype(int))
@@ -339,6 +346,7 @@ def fit(
         observation_times: Union[List, np.ndarray],
         outcome: Union[List, np.ndarray],
     ) -> Any:
+        """Fit (train) the model."""
         (
             static_data_t,
             temporal_data_t,
@@ -436,6 +444,7 @@ def dataloader(
         observation_times: torch.Tensor,
         outcome: torch.Tensor,
     ) -> Tuple[DataLoader, DataLoader]:
+        """Return the train and test `torch` dataloaders."""
         stratify = None
         _, out_counts = torch.unique(outcome, return_counts=True)
         if out_counts.min() > 1:
@@ -569,6 +578,7 @@ def __init__(
         dropout: float = 0,
         nonlin: Nonlin = "relu",
     ) -> None:
+        """Time series layer implementation."""
         super(TimeSeriesLayer, self).__init__()
         temporal_params = {
             "input_size": n_temporal_units_in,
@@ -678,6 +688,7 @@ def __init__(
         self.out.to(device)
 
     def forward(self, static_data: torch.Tensor, temporal_data: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         if self.mode in ["RNN", "LSTM", "GRU"]:
             X_interm, _ = self.temporal_layer(temporal_data)
 
@@ -708,6 +719,7 @@ def __init__(
         nonlin: Nonlin = "relu",
         device: Any = constants.DEVICE,
     ) -> None:
+        """Windowed linear layer implementation."""
         super(WindowLinearLayer, self).__init__()
 
         self.device = device
@@ -726,6 +738,7 @@ def __init__(
 
     @pydantic_utils.validate_arguments(config=pydantic.ConfigDict(arbitrary_types_allowed=True))
     def forward(self, static_data: torch.Tensor, temporal_data: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         if self.n_static_units_in > 0 and len(static_data) != len(temporal_data):
             raise ValueError("Length mismatch between static and temporal data")
 
diff --git a/src/tempor/models/ts_ode.py b/src/tempor/models/ts_ode.py
index d7013956..649fa07d 100644
--- a/src/tempor/models/ts_ode.py
+++ b/src/tempor/models/ts_ode.py
@@ -1,3 +1,5 @@
+"""Time series ODE model implementations."""
+
 from typing import Any, Dict, List, Optional, Tuple, Union
 
 import numpy as np
@@ -68,6 +70,7 @@ def __init__(
         )
 
     def forward(self, t: torch.Tensor, z: torch.Tensor) -> torch.Tensor:  # pylint: disable=unused-argument
+        """Forward pass."""
         z = self.model(z)
 
         z = z.view(*z.shape[:-1], self.n_units_hidden, self.n_units_in)
@@ -114,12 +117,11 @@ def __init__(
         )
 
     def forward(self, t: torch.Tensor, z: torch.Tensor) -> torch.Tensor:  # pylint: disable=unused-argument
+        """Forward pass."""
         return self.model(z)
 
 
 class ReverseGRUEncoder(nn.Module):
-    """Model (encoder and Laplace representation func). Encodes observed trajectory into latent vector."""
-
     def __init__(
         self,
         n_units_in: int,
@@ -127,12 +129,14 @@ def __init__(
         n_units_hidden: int,
         device: Any = DEVICE,
     ):
+        """Model (encoder and Laplace representation func). Encodes observed trajectory into latent vector."""
         super(ReverseGRUEncoder, self).__init__()
         self.gru = nn.GRU(n_units_in, n_units_hidden, 2, batch_first=True)  # type: ignore [no-untyped-call]
         self.linear_out = nn.Linear(n_units_hidden, n_units_latent).to(device)
         nn.init.xavier_uniform_(self.linear_out.weight)
 
     def forward(self, observed_data: torch.Tensor) -> torch.Tensor:
+        """Forward pass."""
         trajs_to_encode = observed_data  # (batch_size, t_observed_dim, observed_dim)
         reversed_trajs_to_encode = torch.flip(trajs_to_encode, (1,))
         out, _ = self.gru(reversed_trajs_to_encode)
@@ -140,10 +144,6 @@ def forward(self, observed_data: torch.Tensor) -> torch.Tensor:
 
 
 class LaplaceFunc(nn.Module):
-    """SphereSurfaceModel : ``C^{b+k} -> C^{bxd}`` - in Riemann Sphere Coords : ``b dim s`` reconstruction terms,
-    `k` is latent encoding dimension, `d` is output dimension.
-    """
-
     def __init__(
         self,
         s_dim: int,
@@ -152,6 +152,9 @@ def __init__(
         n_units_hidden: int = 64,
         device: Any = DEVICE,
     ) -> None:
+        """SphereSurfaceModel : ``C^{b+k} -> C^{bxd}`` - in Riemann Sphere Coords : ``b dim s`` reconstruction terms,
+        `k` is latent encoding dimension, `d` is output dimension.
+        """
         super(LaplaceFunc, self).__init__()
         self.s_dim = s_dim
         self.n_units_out = n_units_out
@@ -174,6 +177,7 @@ def __init__(
         self.to(device)
 
     def forward(self, i: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
+        """Forward pass."""
         out = self.linear_tanh_stack(i.view(-1, self.s_dim * 2 + self.n_units_latent)).view(
             -1, 2 * self.n_units_out, self.s_dim
         )
@@ -419,6 +423,7 @@ def forward(
         temporal_data: torch.Tensor,
         observation_times: torch.Tensor,
     ) -> torch.Tensor:
+        """Forward pass."""
         # sanity
         if torch.isnan(static_data).sum() != 0:
             raise ValueError("NaNs detected in the static data")
@@ -489,6 +494,7 @@ def predict(
         temporal_data: Union[List, np.ndarray, torch.Tensor],
         observation_times: Union[List, np.ndarray, torch.Tensor],
     ) -> np.ndarray:
+        """Make predictions."""
         self.eval()
         with torch.no_grad():
             (
@@ -521,6 +527,7 @@ def predict_proba(
         temporal_data: Union[List, np.ndarray, torch.Tensor],
         observation_times: Union[List, np.ndarray, torch.Tensor],
     ) -> np.ndarray:
+        """Predict probabilities."""
         self.eval()
         if self.task_type != "classification":
             raise RuntimeError("Task valid only for classification")
@@ -552,6 +559,7 @@ def score(
         observation_times: Union[List, np.ndarray, torch.Tensor],
         outcome: Union[List, np.ndarray],
     ) -> float:
+        """Compute default score."""
         y_pred = self.predict(static_data, temporal_data, observation_times)
         outcome = np.asarray(outcome)
         if self.task_type == "classification":
@@ -567,6 +575,7 @@ def fit(
         observation_times: Union[List, np.ndarray, torch.Tensor],
         outcome: Union[List, np.ndarray, torch.Tensor],
     ) -> Any:
+        """Fit (train) the model."""
         (
             static_data_t,
             temporal_data_t,
@@ -672,6 +681,7 @@ def dataloader(
         observation_times: torch.Tensor,
         outcome: torch.Tensor,
     ) -> Tuple[DataLoader, DataLoader]:
+        """Get the train and test `torch` dataloaders."""
         stratify = None
         _, out_counts = torch.unique(outcome, return_counts=True)
         if out_counts.min() > 1:
diff --git a/src/tempor/models/utils.py b/src/tempor/models/utils.py
index e8019615..007682a6 100644
--- a/src/tempor/models/utils.py
+++ b/src/tempor/models/utils.py
@@ -1,3 +1,5 @@
+"""Utilities for the ``models`` package directory."""
+
 import os
 import random
 import warnings
@@ -88,6 +90,7 @@ def enable_reproducibility(
 
 class GumbelSoftmax(nn.Module):
     def __init__(self, tau: float = 0.2, hard: bool = False, dim: int = -1) -> None:
+        """Gumbel-Softmax activation function implementation."""
         super(GumbelSoftmax, self).__init__()
 
         self.tau = tau
@@ -95,7 +98,7 @@ def __init__(self, tau: float = 0.2, hard: bool = False, dim: int = -1) -> None:
         self.dim = dim
 
     def forward(self, logits: torch.Tensor) -> torch.Tensor:
-        # NOTE: nn.functional.gumbel_softmax eps parameter is deprecated.
+        """Forward pass."""
         return nn.functional.gumbel_softmax(logits, tau=self.tau, hard=self.hard, dim=self.dim)
 
 
@@ -110,10 +113,22 @@ def forward(self, logits: torch.Tensor) -> torch.Tensor:
     "softmax": nn.Softmax(dim=-1),
     "gumbel_softmax": GumbelSoftmax(dim=-1),
 }
+"""A map of names (str, keys) to nonlinearity modules (`nn.Module`, values)."""
 tempor.core.utils.ensure_literal_matches_dict_keys(Nonlin, NONLIN_MAP, "Nonlin", "NONLIN_MAP")
 
 
 def get_nonlin(name: Nonlin) -> nn.Module:
+    """Get a nonlinearity `nn.Module` (nonlinearity / activation function) by name.
+
+    Args:
+        name (Nonlin): Nonlinearity name.
+
+    Raises:
+        ValueError: If unknown nonlinearity name.
+
+    Returns:
+        nn.Module: Nonlinearity module.
+    """
     try:
         return NONLIN_MAP[name]
     except KeyError as e:
@@ -128,10 +143,23 @@ def get_nonlin(name: Nonlin) -> nn.Module:
     "SubsetRandomSampler": torch.utils.data.sampler.SubsetRandomSampler,
     "WeightedRandomSampler": torch.utils.data.sampler.WeightedRandomSampler,
 }
+"""A map of names (str, keys) to sampler classes (`Type[torch.utils.data.sampler.Sampler]`, values)."""
 tempor.core.utils.ensure_literal_matches_dict_keys(Samp, SAMPLER_MAP, "Samp", "SAMPLER_MAP")
 
 
 def get_sampler(name: Union[Samp, None], **kwargs: Any) -> Union[torch.utils.data.sampler.Sampler, None]:
+    """Get a sampler by name.
+
+    Args:
+        name (Union[Samp, None]): Sampler name.
+        **kwargs (Any): Sampler initializer kwargs.
+
+    Raises:
+        ValueError: If unknown sampler name.
+
+    Returns:
+        Union[torch.utils.data.sampler.Sampler, None]: Sampler instance.
+    """
     try:
         return SAMPLER_MAP[name](**kwargs) if name is not None else None
     except KeyError as e:
@@ -139,6 +167,15 @@ def get_sampler(name: Union[Samp, None], **kwargs: Any) -> Union[torch.utils.dat
 
 
 def get_device(device: Union[None, int, str, torch.device]) -> torch.device:
+    """Get a `torch` device by name.
+
+    Args:
+        device (Union[None, int, str, torch.device]):
+            Arguments to pass to `torch.device`, or `None` to use the default device.
+
+    Returns:
+        torch.device: Device instance.
+    """
     if device is None:
         return DEVICE
     else:
diff --git a/src/tempor/utils/__init__.py b/src/tempor/utils/__init__.py
index e69de29b..29bacd9f 100644
--- a/src/tempor/utils/__init__.py
+++ b/src/tempor/utils/__init__.py
@@ -0,0 +1,3 @@
+"""Package directory for any utility modules used in the project that do not necessarily fit into
+other package directories.
+"""
diff --git a/src/tempor/utils/serialization.py b/src/tempor/utils/serialization.py
index 3aaf5376..a6959629 100644
--- a/src/tempor/utils/serialization.py
+++ b/src/tempor/utils/serialization.py
@@ -1,3 +1,5 @@
+"""Serialization utilities for models."""
+
 from pathlib import Path
 from typing import Any, Union
 
@@ -5,14 +7,36 @@
 
 
 def save(model: Any) -> bytes:
+    """Save a model to bytes using `cloudpickle`. Companion to `~tempor.utils.serialization.load`.
+
+    Args:
+        model (Any): Model object to be saved.
+
+    Returns:
+        bytes: Model serialized to bytes.
+    """
     return cloudpickle.dumps(model)
 
 
 def load(buff: bytes) -> Any:
+    """Load a model from bytes using `cloudpickle`. Companion to `~tempor.utils.serialization.save`.
+
+    Args:
+        buff (bytes): Model as serialized to bytes (buffer), e.g. loaded from file.
+
+    Returns:
+        Any: The model object.
+    """
     return cloudpickle.loads(buff)
 
 
-def save_to_file(path: Union[str, Path], model: Any) -> Any:
+def save_to_file(path: Union[str, Path], model: Any) -> None:
+    """Save a model to file using `cloudpickle`. Companion to `~tempor.utils.serialization.load_from_file`.
+
+    Args:
+        path (Union[str, Path]): Path to save the model to.
+        model (Any): The model object to be saved.
+    """
     path = Path(path)
     ppath = path.absolute().parent
 
@@ -20,9 +44,17 @@ def save_to_file(path: Union[str, Path], model: Any) -> Any:
         ppath.mkdir(parents=True, exist_ok=True)
 
     with open(path, "wb") as f:
-        return cloudpickle.dump(model, f)
+        cloudpickle.dump(model, f)
 
 
 def load_from_file(path: Union[str, Path]) -> Any:
+    """Load a model from file using `cloudpickle`. Companion to `~tempor.utils.serialization.save_to_file`.
+
+    Args:
+        path (Union[str, Path]): The file path to load the model from.
+
+    Returns:
+        Any: The loaded model object.
+    """
     with open(path, "rb") as f:
         return cloudpickle.load(f)
diff --git a/tutorials/extending/tutorial01_custom_method.ipynb b/tutorials/extending/tutorial01_custom_method.ipynb
index 5ace63fe..5394964b 100644
--- a/tutorials/extending/tutorial01_custom_method.ipynb
+++ b/tutorials/extending/tutorial01_custom_method.ipynb
@@ -427,7 +427,7 @@
     "import numpy as np\n",
     "\n",
     "from tempor.core.plugins import register_plugin\n",
-    "from tempor.methods.core import Params\n",
+    "from tempor.methods.core.params import Params\n",
     "from tempor.data import dataset, samples\n",
     "from tempor.methods.prediction.one_off.classification import BaseOneOffClassifier\n",
     "\n",
diff --git a/tutorials/usage/tutorial09_automl.ipynb b/tutorials/usage/tutorial09_automl.ipynb
index 616a9ee1..752b7946 100644
--- a/tutorials/usage/tutorial09_automl.ipynb
+++ b/tutorials/usage/tutorial09_automl.ipynb
@@ -139,7 +139,7 @@
     }
    ],
    "source": [
-    "from tempor.automl.params import IntegerParams, CategoricalParams\n",
+    "from tempor.methods.core.params import IntegerParams, CategoricalParams\n",
     "\n",
     "# Provide a custom hyperparameter space to search for each type of model.\n",
     "# NOTE: For the sake of speed of this example, we limit epochs to 2.\n",
@@ -599,7 +599,7 @@
     }
    ],
    "source": [
-    "from tempor.automl.params import IntegerParams, CategoricalParams\n",
+    "from tempor.methods.core.params import IntegerParams, CategoricalParams\n",
     "\n",
     "# Provide a custom hyperparameter space to search for each type of model.\n",
     "# These can be provided for the final (predictive) step of the pipeline.\n",


Test patch:
diff --git a/tests/automl/test_pipeline_selector.py b/tests/automl/test_pipeline_selector.py
index 33c617e2..53c78f3c 100644
--- a/tests/automl/test_pipeline_selector.py
+++ b/tests/automl/test_pipeline_selector.py
@@ -163,7 +163,7 @@ def test_sample_hyperparameters(
 
 
 def test_sample_hyperparameters_predictor_override():
-    from tempor.methods.core._params import IntegerParams, Params
+    from tempor.methods.core.params import IntegerParams, Params
 
     task_type = "prediction.one_off.classification"
     predictor = "nn_classifier"
@@ -229,7 +229,7 @@ def test_pipeline_from_hps(task_type, predictor, static_imputers, static_scalers
 
 
 def test_pipeline_from_hps_predictor_override():
-    from tempor.methods.core._params import IntegerParams, Params
+    from tempor.methods.core.params import IntegerParams, Params
 
     task_type = "prediction.one_off.classification"
     predictor = "nn_classifier"
diff --git a/tests/automl/test_seeker.py b/tests/automl/test_seeker.py
index 326e51bc..acdfbe88 100644
--- a/tests/automl/test_seeker.py
+++ b/tests/automl/test_seeker.py
@@ -16,7 +16,7 @@
     TunerType,
 )
 from tempor.data.dataset import PredictiveDataset, TimeToEventAnalysisDataset
-from tempor.methods.core._params import CategoricalParams, IntegerParams
+from tempor.methods.core.params import CategoricalParams, IntegerParams
 
 
 def test_init_fails_estimator_name_def_length_mismatch():
diff --git a/tests/automl/test_tuner.py b/tests/automl/test_tuner.py
index e52e5fc9..4f0f56cd 100644
--- a/tests/automl/test_tuner.py
+++ b/tests/automl/test_tuner.py
@@ -14,11 +14,18 @@
 from tempor.benchmarks import evaluation
 from tempor.data.dataset import PredictiveDataset, TimeToEventAnalysisDataset
 from tempor.methods.core._base_predictor import BasePredictor
-from tempor.methods.core._params import IntegerParams
+from tempor.methods.core.params import IntegerParams
 
 # To ignore warnings in parametrization:
 warnings.filterwarnings("ignore", category=optuna.exceptions.ExperimentalWarning)
 
+try:
+    import cmaes  # noqa: F401  # pylint: disable=unused-import
+
+    cmaes_installed = True
+except ImportError:
+    cmaes_installed = False
+
 
 OPTIMIZE_KWARGS = {"n_trials": 2}
 
@@ -35,19 +42,31 @@
 
 TEST_SAMPLER_SET = [
     optuna.samplers.TPESampler(seed=SEED),
-    pytest.param(optuna.samplers.RandomSampler(seed=SEED), marks=pytest.mark.extra),
-    pytest.param(optuna.samplers.CmaEsSampler(seed=SEED), marks=pytest.mark.extra),
+    pytest.param(
+        optuna.samplers.RandomSampler(seed=SEED),
+        marks=pytest.mark.extra,
+    ),
+    pytest.param(
+        optuna.samplers.CmaEsSampler(seed=SEED),
+        marks=[
+            pytest.mark.extra,
+            pytest.mark.skipif(not cmaes_installed, reason="`cmaes` library not installed"),
+        ],
+    ),
     pytest.param(
         optuna.samplers.QMCSampler(seed=SEED),
         marks=[
+            pytest.mark.extra,
             pytest.mark.skipif(
                 Version(optuna.__version__) < Version("3.0.0"),
                 reason="optuna.samplers.QMCSampler is only available in optuna v3.0.0+",
             ),
-            pytest.mark.extra,
         ],
     ),
-    pytest.param("GridSampler", marks=pytest.mark.extra),
+    pytest.param(
+        "GridSampler",
+        marks=pytest.mark.extra,
+    ),
     # ^ Needs get_grid_by_sampling() call in test to define the grid.
     # NOTE: `optuna.samplers.BruteForceSampler` not currently compatible due to FloatParams
     # infinite search space; MO samplers are not applicable.
diff --git a/tests/methods/core/test_params.py b/tests/methods/core/test_params.py
index 78ec8efb..74b28870 100644
--- a/tests/methods/core/test_params.py
+++ b/tests/methods/core/test_params.py
@@ -4,7 +4,7 @@
 import pytest
 from optuna.trial import Trial
 
-from tempor.methods.core import _params as params
+from tempor.methods.core import params
 
 
 class TestParams:
diff --git a/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py b/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
index 49b00a65..feb82a78 100644
--- a/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
+++ b/tests/methods/prediction/one_off/classification/test_prediction_oneoff_classification_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset
     from tempor.data.samples import StaticSamples
 
diff --git a/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py b/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
index d9a0dae4..5435a5bc 100644
--- a/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
+++ b/tests/methods/prediction/one_off/regression/test_prediction_oneoff_regression_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset
     from tempor.data.samples import StaticSamples
 
diff --git a/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py b/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
index 16247810..89eeb578 100644
--- a/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
+++ b/tests/methods/prediction/temporal/classification/test_prediction_temporal_classification_base.py
@@ -7,7 +7,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py b/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
index 8f78132e..669c2b57 100644
--- a/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
+++ b/tests/methods/prediction/temporal/regression/test_prediction_temporal_regression_base.py
@@ -6,7 +6,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/time_to_event/test_time_to_event_base.py b/tests/methods/time_to_event/test_time_to_event_base.py
index 3fc9fe3f..812082b5 100644
--- a/tests/methods/time_to_event/test_time_to_event_base.py
+++ b/tests/methods/time_to_event/test_time_to_event_base.py
@@ -8,7 +8,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/treatments/one_off/test_treatments_oneoff_base.py b/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
index 651ca7cf..1faa3e7f 100644
--- a/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
+++ b/tests/methods/treatments/one_off/test_treatments_oneoff_base.py
@@ -7,7 +7,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples
 
diff --git a/tests/methods/treatments/temporal/test_treatments_temporal_base.py b/tests/methods/treatments/temporal/test_treatments_temporal_base.py
index a84621bf..8af37c0e 100644
--- a/tests/methods/treatments/temporal/test_treatments_temporal_base.py
+++ b/tests/methods/treatments/temporal/test_treatments_temporal_base.py
@@ -6,7 +6,7 @@
 
 if TYPE_CHECKING:
     from typing_extensions import Self
-    from tempor.methods.core import Params
+    from tempor.methods.core.params import Params
     from tempor.data.dataset import BaseDataset, PredictiveDataset
     from tempor.data.samples import TimeSeriesSamples