Repository: scipy/scipy

Issue:
ENH: Exporting the removed component of detrend()
### Is your feature request related to a problem? Please describe.

Currently, `scipy.signal.detrend()` simply returns the detrended array. However, it would be valuable to also export the component that was removed (i.e. the trend), so it can be used and/or put back later.

The scientific use case for this is that it is common in timeseries analysis to model a signal as a superposition of a deterministic and stochastic part. The deterministic part is often isolated via trend fitting, which `scipy.signal.detrend()` does under the hood, but then jettisons the result where no-one can find it.  



### Describe the solution you'd like.

Export the removed component (trend) as a second, optional array of same dimension as `y`.  In general, one might consider doing the same for other data processing steps that remove part of a signal, so the components can be added back in at the end of an analysis. 

### Describe alternatives you've considered.

Re-writing a detrending function from scratch or hacking `scipy.signal.detrend()` in another package so it does what I want

### Additional context (e.g. screenshots, GIFs)

_No response_


Discussion:
The suggested enhancement here is

> Export the removed component (trend) as a second, optional array of same dimension as y.

One can get that by simply subtracting the output from the input, e.g.

    y = detrend(x)
    trend = x - y

I don't think we need to extend the API for such a simple operation.

A more interesting enhancement would be to return the *parameters* of the trend.  That is, if `type='linear'`,  return the slope and intercept, and if `type='constant`, return the constant. If `type='linear` and `bp` is used, the return value would be a sequence of (slope, intercept) pairs.

I'm not advocating for such a change; I don't know if there is sufficient demand for such a feature to make it worthwhile. 

Resolving patch:
diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py
--- a/scipy/signal/_signaltools.py
+++ b/scipy/signal/_signaltools.py
@@ -1,11 +1,15 @@
 # Author: Travis Oliphant
 # 1999 -- 2002
 
+from __future__ import annotations # Provides typing union operator `|` in Python 3.9
 import operator
 import math
 from math import prod as _prod
 import timeit
 import warnings
+from typing import Literal
+
+from numpy._typing import ArrayLike
 
 from scipy.spatial import cKDTree
 from . import _sigtools
@@ -3503,9 +3507,10 @@ def vectorstrength(events, period):
     return strength, phase
 
 
-def detrend(data, axis=-1, type='linear', bp=0, overwrite_data=False):
-    """
-    Remove linear trend along axis from data.
+def detrend(data: np.ndarray, axis: int = -1,
+            type: Literal['linear', 'constant'] = 'linear',
+            bp: ArrayLike | int = 0, overwrite_data: bool = False) -> np.ndarray:
+    r"""Remove linear or constant trend along axis from data.
 
     Parameters
     ----------
@@ -3532,17 +3537,55 @@ def detrend(data, axis=-1, type='linear', bp=0, overwrite_data=False):
     ret : ndarray
         The detrended input data.
 
+    Notes
+    -----
+    Detrending can be interpreted as substracting a least squares fit polyonimial:
+    Setting the parameter `type` to 'constant' corresponds to fitting a zeroth degree
+    polynomial, 'linear' to a first degree polynomial. Consult the example below.
+
+    See Also
+    --------
+    numpy.polynomial.polynomial.Polynomial.fit: Create least squares fit polynomial.
+
+
     Examples
     --------
+    The following example detrends the function :math:`x(t) = \sin(\pi t) + 1/4`:
+
+    >>> import matplotlib.pyplot as plt
     >>> import numpy as np
-    >>> from scipy import signal
-    >>> rng = np.random.default_rng()
-    >>> npoints = 1000
-    >>> noise = rng.standard_normal(npoints)
-    >>> x = 3 + 2*np.linspace(0, 1, npoints) + noise
-    >>> (signal.detrend(x) - noise).max()
-    0.06  # random
+    >>> from scipy.signal import detrend
+    ...
+    >>> t = np.linspace(-0.5, 0.5, 21)
+    >>> x = np.sin(np.pi*t) + 1/4
+    ...
+    >>> x_d_const = detrend(x, type='constant')
+    >>> x_d_linear = detrend(x, type='linear')
+    ...
+    >>> fig1, ax1 = plt.subplots()
+    >>> ax1.set_title(r"Detrending $x(t)=\sin(\pi t) + 1/4$")
+    >>> ax1.set(xlabel="t", ylabel="$x(t)$", xlim=(t[0], t[-1]))
+    >>> ax1.axhline(y=0, color='black', linewidth=.5)
+    >>> ax1.axvline(x=0, color='black', linewidth=.5)
+    >>> ax1.plot(t, x, 'C0.-',  label="No detrending")
+    >>> ax1.plot(t, x_d_const, 'C1x-', label="type='constant'")
+    >>> ax1.plot(t, x_d_linear, 'C2+-', label="type='linear'")
+    >>> ax1.legend()
+    >>> plt.show()
+
+    Alternatively, NumPy's `~numpy.polynomial.polynomial.Polynomial` can be used for
+    detrending as well:
 
+    >>> pp0 = np.polynomial.Polynomial.fit(t, x, deg=0)  # fit degree 0 polynomial
+    >>> np.allclose(x_d_const, x - pp0(t))  # compare with constant detrend
+    True
+    >>> pp1 = np.polynomial.Polynomial.fit(t, x, deg=1)  # fit degree 1 polynomial
+    >>> np.allclose(x_d_linear, x - pp1(t))  # compare with linear detrend
+    True
+
+    Note that `~numpy.polynomial.polynomial.Polynomial` also allows fitting higher
+    degree polynomials. Consult its documentation on how to extract the polynomial
+    coefficients.
     """
     if type not in ['linear', 'l', 'constant', 'c']:
         raise ValueError("Trend type must be 'linear' or 'constant'.")


Test patch: