Coding Style Guide with Examples

This guide follows mostly: https://github.com/numpy/numpy/blob/v1.23.5/doc/example.py.

In the Python code blocks below, some remarks are included using JavaScript style notation <!– comment –>. They provide additional information regarding the code that is being presented as example.

Module docstring

The module should start with its own docstring in the first line. The example below illustrates show it should be implemented.

"""
Here comes the module description

Detailed introduction and tutorial of method goes here.

You can even link tutorials here, if appropriate.

If you have references you can insert them here, otherwise in the corresponding
functions, methods or classes.

For a working example see
:py:mod:`unitary_event_analysis.py<elephant.unitary_event_analysis>`
"""

Function docstring. Naming conventions

The function below illustrates how arguments and functions should be named throughout Elephant.

def pair_of_signals_example(spiketrain_i, spiketrain_j):
    # Add '_i' and '_j' suffixes to a pair of signals, spiketrains or any
    # other variables that come in pairs.

def perfect_naming_of_parameters(spiketrains, spiketrain, reference_spiketrain,
                     target_spiketrain, signal, signals, max_iterations,
                     min_threshold, n_bins, n_surrogates, bin_size, max_size,
                     time_limits, time_range, t_start, t_stop, period, order,
                     error, capacity, source_matrix, cov_matrix,
                     selection_method='aic'
                     ):
    r"""
    Full example of the docstring and naming conventions.

    Function names should be in lowercase, with words written in full, and
    separated by underscores. Exceptions are for common abbreviations, such
    as "psd" or "isi". But words such as "coherence" must be written in full,
    and not truncated (e.g. "cohere").

    If the truncation or abbreviation not in conformity to this naming
    convention was adopted to maintain similarity to a function used
    extensively in another language or package, mention this in the "Notes"
    section, like the comment below:
    <!--
    Notes
    -----
    This function is similar to `welch_cohere` function in MATLAB.
    -->

    The rationale for the naming of each parameter in this example will be
    explained in the relevant "Parameters" section. Class parameters and
    attributes also follow the same naming convention.

    Parameters
    ----------
    <!-- As a general rule, each word is written in full lowercase, separated
    by underscores. Special cases apply according to the examples below -->
    spiketrains : neo.SpikeTrain or list of neo.SpikeTrain
        Within Elephant, this is how to name an input parameter that contains
        at least one spike train. The parameter name is in plural (i.e.,
        `spiketrains`). The function will deal with converting a single
        `neo.SpikeTrain` to a list of `neo.SpikeTrain` if needed.
        Note that although these are two words, they are NOT separated by
        underscore because Neo does not use underscore, and Elephant must keep
        compatibility. Do not use names such as `sts`, `spks`, or
        `spike_trains`.
    spiketrain: neo.SpikeTrain
        If the function EXPLICITLY requires only a single spike train, then
        the parameter should be named in singular (i.e., `spiketrain`). Do
        not use names such as `st`, `spk`, or `spike_train`.
    reference_spiketrain : neo.SpikeTrain
        If a function uses more than one parameter with single spike trains,
        then each parameter name begins with a meaningful name,
        followed by "_spiketrain" in singular form.
    target_spiketrain: neo.SpikeTrain
        Second parameter that is a single spike train. Note that the difference
        from `reference_spiketrain` is indicated by a meaningful name at the
        beginning.
    signal : neo.AnalogSignal
        If a single `neo.AnalogSignal` object is passed to the function, even if
        it contains several signals (arrays).
    signals : list of neo.AnalogSignal
        If the parameter is a container that has at least one `neo.AnalogSignal`
        object. The name of the parameter is `signals` (plural).
    max_iterations : int
        Parameters that represent a maximum value should start with "max_"
        prefix, followed by the description as a full word. Therefore, do not
        use names such as `max_iter` or `maxiter`.
    min_threshold : float
        Same case as for maximum. Parameters that represent a minimum value
        should start with "min_" prefix, followed by the description as a full
        word. Therefore, do not use names such as `min_thr` or `minthr`.
    n_bins : int
        Parameters that represent a number should start with the prefix "n_".
        Do not use `numbins`, `bin_number`, or `num_bins`. The prefix should
        be followed by a meaningful word in full.
    n_surrogates : int
        The description should always be meaningful an without abbreviations.
        Therefore, do not use terms as `n` or `n_surr`, that are not
        immediately understood.
    bin_size : pq.Quantity or int
        Separate the words by underscore. Do not use `bin_size`. Old functions
        which use `binsize` are deprecated.
    max_size : float
        Another example showing that words should be separated by underscores.
        This intersects with the naming convention for a maximum value.
    time_limits: list or tuple
        For parameters that define minimum and maximum values as a list or
        tuple (e.g., [-2, 2]), the parameter must start with a meaningful
        word followed by the suffix "_limits". Preferentially, one should use
        two separated parameters (e.g., `max_time` and `min_time` following
        the convention for maximum and minimum already mentioned). But should
        the function require the definition of limits in this form, use the
        name `_limits` and not `_range` (see next parameter).
    time_range: list
        For parameters that behave like a Python range (e.g. [1, 2, 3, 4])), in
        the sense that it is a sequence, not only the lower and upper limits
        as in the example above, the parameter should start with a meaningful
        name followed by the suffix "_range".
    t_start : pq.Quantity
        Standard name within Elephant for defining starting times.
    t_stop : pq.Quantity
        Standard name within Elephant for defining stopping times.
    period : pq.Quantity
        Oscillation period.
        Always use informative names. In this case, one could name the
        parameter as simply as `T`, since this is standard for referring to
        periods. If the function is implementing computations based on a paper
        that has a formula with a variable "T", acknowledge this after
        describing the formula in the docstring. Therefore, write a sentence
        like "`period` refers to :math:`T`"
        If the Elephant function uses an external function (such as from
        `scipy`), and such function has an argument named `T`, also
        acknowledge this in the docstring. Therefore, write a sentence like
        "`period` is forwarded as argument `T` of `scipy.uses_T` function".
        If the external function already has an informative parameter name
        (such as `period`), the same parameter name can be used in the Elephant
        function if forwarded.
        If several input parameters are forwarded or are members
        of a formula, the docstring can present them together as a list.
        But always use informative names, not single letter names if this is
        how they are described in the paper or implemented in another function.
    order : int
        Order of the Butterworth filter.
        This is an example of how the `N` parameter of `scipy.signal.butter`
        function could be provided by the user of the Elephant function.
        The docstring would present a text similar to
        "`order` is passed as the `N` argument for `scipy.signal.butter` function".
        Also, in the code implementation, use keyword arguments to make this
        explicit (see the implementation of the function below)
    error : float
        In the case the function has an input parameter that corresponds to a
        greek letter in a formula (in a paper, for instance) always use the
        meaning of the greek letter. Therefore, should :math:`\epsilon` refer
        to the error in the formula, the parameter should be named `error`. As
        already mentioned, this is acknowledged in the docstring after the
        description of the formula.
    capacity : float
        Capacity value.
        When using parameters based on a paper (which, e.g., derives some
        formula), and the parameter's name in this paper is a single letter
        (such as `C` for capacity), always use the meaning
        of the letter. Therefore, the parameter should be named `capacity`,
        not `C`. Acknowledge this in the docstring as already mentioned.
    source_matrix: np.ndarray
        Parameters that are matrices should end with the suffix "_matrix", and
        start with a meaningful name.
    cov_matrix: np.ndarray
        A few exceptions allow the use of abbreviations instead of full words
        in the name of the parameter. These are:
        * "cov" for "covariance" (e.g., `cov_matrix`)
        * "lfp" for "local_field_potential" (e.g. `lfp_signal`)
        * "corr" for "correlation" (e.g. `corr_matrix`).
        THESE EXCEPTIONS ARE NOT ACCEPTED FOR FUNCTION NAMES. Therefore, a
        parameter would be named `cov_matrix`, but the function would be named
        `calculate_covariance_matrix`. If the function name becomes very long,
        then an alias may be created and described appropriately in the "Notes"
        section, as mentioned above. For aliases, see example below.
    selection_method : {'aic', 'bic'}
        Metric for selecting the autoregressive model.
        If 'aic', uses the Akaike Information Criterion (AIC).
        If 'bic', uses Bayesian Information Criterion (BIC).
        Default: 'bic', because it is more reliable than AIC due to the
        mathematical properties (see Notes [3]).
        <!-- Note that the default value that comes in the last line is
        followed by comma and a brief reasoning for defining the default
        `selection_method`). -->

    <!-- Other remarks:
    1. Do not use general parameter names, such as `data` or `matrix`.
    2. Do not use general result names, such as `result` or `output`.
    3. Avoid capitalization (such as the examples mentioned for parameters
       such as `T` for period, or `C` for capacity or a correlation matrix.
    -->

    Returns
    -------
        frequency : float
            The frequency of the signal.
        filtered_signal : np.ndarray
            Signal filtered using Butterworth filter.

    Notes
    -----
    1. Frequency is defined as:

    .. math::

        f = \frac{1}{T}

       `period` corresponds to :math:`T`

    2. `order` is passed as the `N` parameter when calling
       `scipy.signal.butter`.
    3. According to [1]_, BIC should be used instead of AIC for this
       computation. The brief rationale is .......

    References
    ----------
    .. [1] Author, "Why BIC is better than AIC for AR model", Statistics,
           vol. 1, pp. 1-15, 1996.

    """
    # We use Butterworth filter from scipy to perform some calculation.
    # Note that parameter `N` is passed using keys, taking the value of the
    # `order` input parameter
    filtered_signal = scipy.signal.butter(N=order, ...)

    # Here we calculate a return value using a function variable. Note that
    # this variable is named in the "Returns" section
    frequency = 1 / period
    return frequency, filtered_signal

Class docstring

Class docstrings follow function docstring format. Here is an example.

class MyClass(object):  # Classes use CamelCase notation
    """
    One line description of class.

    Long description of class, may span several lines. Possible sections are
    the same as for a function doc, with additional "Attributes" and "Methods"
    after "Parameters" (cf. numpydoc guide). Do not put a blank line after
    section headers, do put a blank line at the end of a long docstring.

    When explaining the algorithm, you can use mathematical notation, e.g.:

    .. math::

        E = m c^2

    To insert an equation use `.. math::` and surround the whole expression in
    blank lines. To use math notation in-line, write :math:`E` corresponds to
    energy and :math:`m` to mass. Embed expressions after `:math:` in
    backticks, e.g. :math:`x^2 + y^2 = z^2`.

    To refer to a paper in which formula is described, use the expression
    "see [1]_" - it will become an interactive link on the readthedocs website.
    The underscore after the closing bracket is mandatory for the link to
    work.

    To refer to a note in the "Notes" section, simply write "see Notes [1]".

    Variable, module, function, and class names should be written
    between single back-ticks (`kernels.AlphaKernel`), NOT *bold*.

    For common modules such as Numpy and Quantities, use the notation
    according to the import statement. For example:
    "this function uses `np.diff`", not "uses `numpy.diff`".

    Prefixes for common packages in Elephant are the following:

    1. Neo objects = neo (e.g. `neo.SpikeTrain`)
    2. Numpy = np (e.g. `np.ndarray`)
    3. Quantities = pq (e.g. `pq.Quantity`)

    For other objects, list the full path to the object (e.g., for the
    BinnedSpikeTrain, this would be `elephant.conversion.BinnedSpikeTrain`)

    For None and NaNs, do not use backticks. NaN is referred as np.NaN (i.e.,
    with the Numpy prefix "np").

    Use backticks also when referring to arguments of a function (e.g., `x` or
    `y`), and :attr:`attribute_name` when referring to attributes of a class
    object in docstrings of this class.

    To refer to attributes of other objects, write
    `other_object.relevant_attribute` (e.g. `neo.SpikeTrain.t_stop`).

    When mentioning a function from other module, type `other_module.function`
    (without parentheses after the function name; e.g., `scipy.signal.butter`).

    If you refer values to True/False/None, do not use backticks, unless an
    emphasis is needed. In this case, write `True` and not bold, like **True**.

    Parameters
    ----------
    <!-- List the arguments of the constructor (__init__) here!
    Arguments must come in the same order as in the constructor or function -->
    parameter : int or float
        Description of parameter `parameter`. Enclose variables in single
        backticks. The colon must be preceded by a space.
    no_type_parameter
        Colon omitted if the type is absent.
    x : float
        The X coordinate.
    y : float
        The Y coordinate.
        Default: 1.0.  <!-- not "Default is 1.0." (it is just a convention) -->
    z : float or int or pq.Quantity
        This is Z coordinate.
        If it can take multiple types, separate them by "or", do not use commas
        (numpy style).
        If different actions will happen depending on the type of `z`, explain
        it briefly here, not in the main text of the function/class docstring.
    s : {'valid', 'full', 'other'}
        This is the way to describe a list of possible argument values, if the
        list is discrete and predefined (typically concerns strings).
        If 'valid', the function performs some action.
        If 'full', the function performs another action.
        If 'other', the function will ignore the value defined in `z`.
        Default: 'valid'.
    spiketrains : neo.SpikeTrain or list of neo.SpikeTrain or np.ndarray
        When the parameter can be a container (such as list or tuple), you can
        specify the type of elements using "of". But use the Python type name
        (do not add "s" to make it plural; e.g., do not write
        "list of neo.SpikeTrains" or "list of neo.SpikeTrain objects").
    counts_matrix : (N, M) np.ndarray
        This is the way to indicate dimensionality of the required array
        (i.e.,if the function only works with 2D-arrays). `N` corresponds to
        the number of rows and `M` to the number of columns. Refer to the same
        `N` and `M` to describe the dimensions of the returned values when
        they are determined by the dimensions of the parameter.
    is_true : bool
        True, if 1.
        False, if 0.
        Default: True.
    other_parameter : int
        Some value.
        If value is None and the function takes some specific action (e.g.,
        calculate some value based on the other inputs), describe here.
        Default: None.

    Attributes
    ----------
    <!-- Here list the attributes of class object which are not simply copies
    of the constructor parameters. Property decorators (@property) are also
    considered attributes -->
    a : list
        This is calculated based on `x` and `y`.
    b : int
        This is calculated on the way, during some operations.

    Methods
    -------
    <!--  Here list the most important/useful class methods (not all the
    methods) -->

    Returns
    -------
    <!-- This section is rarely used in class docstrings, but often in
    function docs. Follow the general recommendation of numpydoc.
    If there is more than one returned value, use variable names for the
    returned value, like `error_matrix` below. -->
    error_matrix : np.ndarray
        A matrix is stored in a variable called `error_matrix`, containing
        errors estimated from some calculations. The function "return"
        statement then returns the variable (e.g. "return error_matrix").
        Format is the same as for any parameter in section "Parameters".
        Use meaningful names, not general names such as `output` or `result`.
    list
        The returned object is created on the fly and is never assigned to
        a variable (e.g. "return [1, 2, 3]"). Simply name the type and
        describe the content. This should be used only if the function returns
        a single value.
    dict
        key_1 : type
            Description of key_1, formatted the same as in "Parameters".
        key_2 : type
            Description of key_2
    particular_matrix : (N, N, M) np.ndarray
        The dimensionality of this array depends on the dimensionality of
        `counts_matrix` input parameter. Note that `N` and `M` are used since
        these were the names of the dimensions of `counts_matrix` in the
        "Parameters" section.
    list_variable : list of np.ndarray
        Returns a list of numpy arrays.
    signal : int
        Description of `signal`.

    Raises
    ------
    <!-- List the errors explicitly raised by the constructor (raise
    statements), even if they are in fact raised by other Elephant functions
    called inside the constructor. Enumerate them in alphabetical order. -->
    TypeError
        If `x` is an `int` or None.
        If `y` is not a `float`.
    ValueError
        If this and that happens.

    Warns
    -----
    <!-- Here apply the same rules as for "Raises". -->
    UserWarning
        If something may be wrong but does not prevent execution of the code.
        The default warning type is UserWarning.

    Warning
    -------
    <!-- Here write a message to the users to warn them about something
    important.
    Do not enumerate Warnings in this section! -->

    See Also
    --------
    <!-- Here refer to relevant functions (also from other modules). Follow
    numpydoc recommendations.
    If the function name is not self-explanatory, you can add a brief
    explanation using a colon separated by space.
    This items will be placed as links to the documentation of the function
    referred.
    -->
    statistics.isi
    scipy.signal.butter : Butterworth filter

    Notes
    -----
    <!-- Here you can add some additional explanations etc. If you have several
    short notes (at least two), use a list -->
    1. First remark.
    2. Second much longer remark, which will span several lines. To refer to a
       note in other parts of the docstring, use a phrase like "See Notes [2]".
       To make sure that the list displays correctly, keep the indentation to
       match the first word after the point (as in this text).
    3. If you want to explain why the default value of an argument is
       something particular, you can give a more elaborate explanation here.
    4. If the function has an alias (see the last function in this file), the
       information about it should be in this section in the form:
       Alias: bla.
       Aliases should be avoided.
    5. Information about validation should be here, and insert bibliographic
       citation in the "References". Also specify in parenthesis the unit test
       that implements the validation. Example:
       "This function reproduces the paper Riehle et al., 1997 [2]_.
       (`UETestCase.test_Riehle_et_al_97_UE`)."
    6. Do not create new section names, because they will not be displayed.
       Place the relevant information here instead.
    7. This is an optional section that provides additional information about
       the code, possibly including a discussion of the algorithm. This
       section may include mathematical equations, written in LaTeX format.
       Inline: :math:`x^2`. An equation:

       .. math::

       x(n) * y(n) \Leftrightarrow X(e^{j\omega } )Y(e^{j\omega } )

    8. Python may complain about backslashes in math notation in docstrings.
       To prevent the complains, precede the whole docstring with "r" (raw
       string).
    9. Images are allowed, but should not be central to the explanation;
       users viewing the docstring as text must be able to comprehend its
       meaning without resorting to an image viewer. These additional
       illustrations are included using:

        .. image:: filename

    References
    ----------
    .. [1] Smith J., "Very catchy title," Elephant 1.0.0, 2020. The ".." in
           front makes the ref referencable in other parts of the docstring.
           The indentation should match the level of the first word AFTER the
           number (in this case "Smith").

    Examples
    --------
    <!-- If applicable, provide some brief description of the example, then
    leave a blank line.
    If the second example uses an import that was already used in the first
    example, do not write the import again.
    Examples should be very brief, and should avoid plotting. If plotting
    is really needed, use simple matplotlib plots, that take only few lines.
    More complex examples, that require lots of plotting routines (e.g.,
    similar to Jupyter notebooks), should be placed as tutorials, with links
    in the docstring. Examples should not load any data, but only use easy
    generated data.
    Finally, avoid using abbreviations in examples, such as
    "import elephant.conversion as conv" -->

    >>> import neo
    >>> import numpy as np
    >>> import quantities as pq
    ...
    ... # This is a way to make a blank line within the example code.
    >>> st = neo.SpikeTrain([0, 1, 2, 3] * pq.ms, t_start=0 * pq.ms,
    ...                     t_stop=10 * pq.ms, sampling_rate=1 * pq.Hz)
    ... # Use "..." also as a continuation line.
    >>> print(st)
    SpikeTrain

    Here provide a brief description of a second example. Separate examples
    with a blank line even if you do not add any description.

    >>> import what_you_need
    ...
    >>> st2 = neo.SpikeTrain([5, 6, 7, 8] * pq.ms, t_start=0 * pq.ms,
    ...                      t_stop=10 * pq.ms, sampling_rate=1 * pq.Hz)
    >>> sth = what_you_need.function(st2)
    >>> sth_else = what_you_need.interesting_function(sth)

    """

    def __init__(self, parameter):
        """
        Constructor
        (actual documentation is in class documentation, see above!)
        """
        self.parameter = parameter
        self.function_a()  # creates new attribute of self 'a'

    def function_a(self, parameter, no_type_parameter, spiketrains,
                   is_true=True, string_parameter='C', other_parameter=None):
        """
        One-line short description of the function.

        Long description of the function. Details of what the function is doing
        and how it is doing it. Used to clarify functionality, not to discuss
        implementation detail or background theory, which should rather be
        explored in the "Notes" section below. You may refer to the parameters
        and the function name, but detailed parameter descriptions still
        belong in the "Parameters" section.

        Parameters
        ----------
        <!-- See class docstring above -->

        Returns
        -------
        <!-- See class docstring above -->

        Raises
        ------
        <!-- See class docstring above.
        List only exceptions explicitly raised by the function -->

        Warns
        -----
        <!-- See class docstring above. -->

        See Also
        --------
        <!-- See class docstring above  -->

        Notes
        -----
        <!-- See class docstring above -->

        References
        ----------
        <!-- See class docstring above -->

        Examples
        --------
        <!-- See class docstring above -->

        """

        # Variables use underscore notation
        dummy_variable = 1
        a = 56  # This mini comment uses two spaces after the code!

        # Textual strings use double quotes
        error = "An error occurred. Please fix it!"
        # Textual strings are usually meant to be printed, returned etc.

        # Non-textual strings use single quotes
        default_character = 'a'
        # Non textual strings are single characters, dictionary keys and other
        # strings not meant to be returned or printed.

        # Normal comments are proceeded by a single space, and begin with a
        # capital letter
        dummy_variable += 1

        # Longer comments can have several sentences. These should end with a
        # period. Just as in this example.
        dummy_variable += 1

    # Class functions need only 1 blank line.
    # This function is deprecated. Add a warning!
    def function_b(self, **kwargs):
        """
        This is a function that does b.

        .. deprecated:: 0.4
          `function_b` will be removed in elephant 1.0, it is replaced by
          `function_c` because the latter works also with Numpy Ver. 1.6.

        Parameters
        ----------
        kwargs : dict
            kwarg1 : type
                Same style as docstring of class `MyClass`.
            kwarg2 : type
                Same style as docstring of class `MyClass`.

        """
        pass