# Polynomials

Polynomials in NumPy can be created, manipulated, and even fitted using the convenience classes of the `numpy.polynomial` package, introduced in NumPy 1.4.

Prior to NumPy 1.4, `numpy.poly1d` was the class of choice and it is still available in order to maintain backward compatibility. However, the newer `polynomial package` is more complete and its convenience classes provide a more consistent, better-behaved interface for working with polynomial expressions. Therefore `numpy.polynomial` is recommended for new coding.

Note

Terminology

The term polynomial module refers to the old API defined in `numpy.lib.polynomial`, which includes the `numpy.poly1d` class and the polynomial functions prefixed with poly accessible from the `numpy` namespace (e.g. `numpy.polyadd`, `numpy.polyval`, `numpy.polyfit`, etc.).

The term polynomial package refers to the new API defined in `numpy.polynomial`, which includes the convenience classes for the different kinds of polynomials (`numpy.polynomial.Polynomial`, `numpy.polynomial.Chebyshev`, etc.).

## Transitioning from `numpy.poly1d` to `numpy.polynomial`

As noted above, the `poly1d class` and associated functions defined in `numpy.lib.polynomial`, such as `numpy.polyfit` and `numpy.poly`, are considered legacy and should not be used in new code. Since NumPy version 1.4, the `numpy.polynomial` package is preferred for working with polynomials.

### Quick Reference

The following table highlights some of the main differences between the legacy polynomial module and the polynomial package for common tasks. The `Polynomial` class is imported for brevity:

    from numpy.polynomial import Polynomial

How to… | Legacy (`numpy.poly1d`) | `numpy.polynomial`
Create a polynomial object from coefficients [1] | `p = np.poly1d([1, 2, 3])` | `p = Polynomial([3, 2, 1])`
Create a polynomial object from roots | `r = np.poly([-1, 1])` `p = np.poly1d(r)` | `p = Polynomial.fromroots([-1, 1])`
Fit a polynomial of degree `deg` to data | `np.polyfit(x, y, deg)` | `Polynomial.fit(x, y, deg)`

[1]

Note the reversed ordering of the coefficients

### Transition Guide

There are significant differences between `numpy.lib.polynomial` and `numpy.polynomial`. The most significant difference is the ordering of the coefficients for the polynomial expressions. The various routines in `numpy.polynomial` all deal with series whose coefficients go from degree zero upward, which is the reverse order of the poly1d convention. The easy way to remember this is that indices correspond to degree, i.e., `coef[i]` is the coefficient of the term of degree i.

Though the difference in convention may be confusing, it is straightforward to convert from the legacy polynomial API to the new. For example, the following demonstrates how you would convert a `numpy.poly1d` instance representing the expression \(x^{2} + 2x + 3\) to a `Polynomial` instance representing the same expression:

    >>> p1d = np.poly1d([1, 2, 3])
    >>> p = np.polynomial.Polynomial(p1d.coef[::-1])

In addition to the `coef` attribute, polynomials from the polynomial package also have `domain` and `window` attributes. These attributes are most relevant when fitting polynomials to data, though it should be noted that polynomials with different `domain` and `window` attributes are not considered equal, and can’t be mixed in arithmetic:

    >>> p1 = np.polynomial.Polynomial([1, 2, 3])
    >>> p1
    Polynomial([1., 2., 3.], domain=[-1,  1], window=[-1,  1], symbol='x')
    >>> p2 = np.polynomial.Polynomial([1, 2, 3], domain=[-2, 2])
    >>> p1 == p2
    False
    >>> p1 + p2
    Traceback (most recent call last):
        ...
    TypeError: Domains differ

See the documentation for the convenience classes for further details on the `domain` and `window` attributes.

Another major difference between the legacy polynomial module and the polynomial package is polynomial fitting. In the old module, fitting was done via the `polyfit` function. In the polynomial package, the `fit` class method is preferred. For example, consider a simple linear fit to the following data:

    In [1]: rng = np.random.default_rng()

    In [2]: x = np.arange(10)

    In [3]: y = np.arange(10) + rng.standard_normal(10)

With the legacy polynomial module, a linear fit (i.e. polynomial of degree 1) could be applied to these data with `polyfit`:

    In [4]: np.polyfit(x, y, deg=1)
    Out[4]: array([0.89217112, 0.20829838])

With the new polynomial API, the `fit` class method is preferred:

    In [5]: p_fitted = np.polynomial.Polynomial.fit(x, y, deg=1)

    In [6]: p_fitted
    Out[6]: Polynomial([4.22306843, 4.01477004], domain=[0., 9.], window=[-1.,  1.], symbol='x')

Note that the coefficients are given in the scaled domain defined by the linear mapping between the `window` and `domain`. `convert` can be used to get the coefficients in the unscaled data domain.

    In [7]: p_fitted.convert()
    Out[7]: Polynomial([0.20829838, 0.89217112], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

## Documentation for the `polynomial` Package

In addition to standard power series polynomials, the polynomial package provides several additional kinds of polynomials including Chebyshev, Hermite (two subtypes), Laguerre, and Legendre polynomials. Each of these has an associated convenience class available from the `numpy.polynomial` namespace that provides a consistent interface for working with polynomials regardless of their type.

- Using the Convenience Classes

Documentation pertaining to specific functions defined for each kind of polynomial individually can be found in the corresponding module documentation:

- Power Series (`numpy.polynomial.polynomial`)
- Chebyshev Series (`numpy.polynomial.chebyshev`)
- Hermite Series, “Physicists” (`numpy.polynomial.hermite`)
- HermiteE Series, “Probabilists” (`numpy.polynomial.hermite_e`)
- Laguerre Series (`numpy.polynomial.laguerre`)
- Legendre Series (`numpy.polynomial.legendre`)
- Polyutils

## Documentation for Legacy Polynomials

- Poly1d
  - Basics
  - Fitting
  - Calculus
  - Arithmetic
  - Warnings