diff --git a/.gitignore b/.gitignore index e7a401284741..df7f084e3645 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ Thumbs.db doc/source/savefig/ doc/source/**/generated/ doc/source/release/notes-towncrier.rst +doc/source/.jupyterlite.doit.db # Things specific to this project # ################################### diff --git a/doc/Makefile b/doc/Makefile index 910da1e06e61..545b10de3384 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -50,6 +50,8 @@ help: clean: -rm -rf build/* + -rm -rf source/.jupyterlite.doit.db + -rm -rf source/contents/*.ipynb find . -name generated -type d -prune -exec rm -rf "{}" ";" gitwash-update: diff --git a/doc/neps/roadmap.rst b/doc/neps/roadmap.rst index fb8602981661..b4eccd0dbfe1 100644 --- a/doc/neps/roadmap.rst +++ b/doc/neps/roadmap.rst @@ -75,7 +75,8 @@ planned improvements. Adding more tutorials is underway in the `numpy-tutorials repo `__. We also intend to make all the example code in our documentation interactive - -work is underway to do so via ``jupyterlite-sphinx`` and Pyodide. +work is underway to do so via ``jupyterlite-sphinx`` and Pyodide. NumPy 2.3.0 +provides interactive documentation for examples as a pilot for this effort. Our website (https://numpy.org) is in good shape. Further work on expanding the number of languages that the website is translated in is desirable. As are diff --git a/doc/release/upcoming_changes/26745.highlight.rst b/doc/release/upcoming_changes/26745.highlight.rst new file mode 100644 index 000000000000..5636f919c80d --- /dev/null +++ b/doc/release/upcoming_changes/26745.highlight.rst @@ -0,0 +1,10 @@ +Interactive examples in the NumPy documentation +----------------------------------------------- + +The NumPy documentation includes a number of examples that +can now be run interactively in your browser using WebAssembly +and Pyodide. + +Please note that the examples are currently experimental in +nature and may not work as expected for all methods in the +public API. diff --git a/doc/source/_static/numpy.css b/doc/source/_static/numpy.css index 180dec530649..5b718a651e57 100644 --- a/doc/source/_static/numpy.css +++ b/doc/source/_static/numpy.css @@ -1,10 +1,11 @@ @import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Open+Sans:ital,wght@0,400;0,600;1,400;1,600&display=swap'); .navbar-brand img { - height: 75px; + height: 75px; } + .navbar-brand { - height: 75px; + height: 75px; } body { @@ -71,4 +72,43 @@ div.admonition-legacy>.admonition-title::after { div.admonition-legacy>.admonition-title { background-color: var(--pst-color-warning-bg); -} \ No newline at end of file +} + +/* Buttons for JupyterLite-enabled interactive examples */ + +.try_examples_button { + color: white; + background-color: var(--pst-color-info); + border: none; + padding: 5px 10px; + border-radius: 0.25rem; + margin-top: 3px; /* better alignment under admonitions */ + margin-bottom: 5px !important; /* fix uneven button sizes under admonitions */ + box-shadow: 0 2px 5px rgba(108, 108, 108, 0.2); + font-weight: bold; + font-size: small; +} + +/* Use more acccessible colours for text in dark mode */ +[data-theme=dark] .try_examples_button { + color: black; +} + +.try_examples_button:hover { + transform: scale(1.02); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + cursor: pointer; +} + +.try_examples_button_container { + display: flex; + justify-content: flex-start; + gap: 10px; + margin-bottom: 20px; +} + +/* Better gaps for examples buttons under admonitions */ + +.try_examples_outer_iframe { + margin-top: 0.4em; +} diff --git a/doc/source/conf.py b/doc/source/conf.py index 44c3a85380de..d76bf1b11a4b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -89,6 +89,7 @@ class PyTypeObject(ctypes.Structure): 'sphinx_copybutton', 'sphinx_design', 'sphinx.ext.imgconverter', + 'jupyterlite_sphinx', ] skippable_extensions = [ @@ -601,4 +602,17 @@ class NumPyLexer(CLexer): ('c:identifier', 'PyHeapTypeObject'), ] +# ----------------------------------------------------------------------------- +# Interactive documentation examples via JupyterLite +# ----------------------------------------------------------------------------- +global_enable_try_examples = True +try_examples_global_button_text = "Try it in your browser!" +try_examples_global_warning_text = ( + "NumPy's interactive examples are experimental and may not always work" + " as expected, with high load times especially on low-resource platforms," + " and the version of NumPy might not be in sync with the one you are" + " browsing the documentation for. If you encounter any issues, please" + " report them on the" + " [NumPy issue tracker](https://github.com/numpy/numpy/issues)." +) diff --git a/doc/source/jupyter_lite_config.json b/doc/source/jupyter_lite_config.json new file mode 100644 index 000000000000..6b25be20912a --- /dev/null +++ b/doc/source/jupyter_lite_config.json @@ -0,0 +1,5 @@ +{ + "LiteBuildConfig": { + "no_sourcemaps": true + } +} diff --git a/doc/source/reference/arrays.classes.rst b/doc/source/reference/arrays.classes.rst index 593d5541877b..7ef59277f298 100644 --- a/doc/source/reference/arrays.classes.rst +++ b/doc/source/reference/arrays.classes.rst @@ -405,15 +405,19 @@ alias for "matrix "in NumPy. Example 1: Matrix creation from a string +.. try_examples:: + >>> import numpy as np >>> a = np.asmatrix('1 2 3; 4 5 3') >>> print((a*a.T).I) [[ 0.29239766 -0.13450292] - [-0.13450292 0.08187135]] + [-0.13450292 0.08187135]] Example 2: Matrix creation from a nested sequence +.. try_examples:: + >>> import numpy as np >>> np.asmatrix([[1,5,10],[1.0,3,4j]]) matrix([[ 1.+0.j, 5.+0.j, 10.+0.j], @@ -421,6 +425,8 @@ Example 2: Matrix creation from a nested sequence Example 3: Matrix creation from an array +.. try_examples:: + >>> import numpy as np >>> np.asmatrix(np.random.rand(3,3)).T matrix([[4.17022005e-01, 3.02332573e-01, 1.86260211e-01], @@ -457,6 +463,8 @@ array actually get written to disk. Example: +.. try_examples:: + >>> import numpy as np >>> a = np.memmap('newfile.dat', dtype=float, mode='w+', shape=1000) @@ -605,6 +613,8 @@ This default iterator selects a sub-array of dimension :math:`N-1` from the array. This can be a useful construct for defining recursive algorithms. To loop over the entire array requires :math:`N` for-loops. +.. try_examples:: + >>> import numpy as np >>> a = np.arange(24).reshape(3,2,4) + 10 >>> for val in a: @@ -629,8 +639,9 @@ As mentioned previously, the flat attribute of ndarray objects returns an iterator that will cycle over the entire array in C-style contiguous order. +.. try_examples:: + >>> import numpy as np - >>> a = np.arange(24).reshape(3,2,4) + 10 >>> for i, val in enumerate(a.flat): ... if i%5 == 0: print(i, val) 0 10 @@ -654,9 +665,12 @@ N-dimensional enumeration Sometimes it may be useful to get the N-dimensional index while iterating. The ndenumerate iterator can achieve this. +.. try_examples:: + >>> import numpy as np >>> for i, val in np.ndenumerate(a): - ... if sum(i)%5 == 0: print(i, val) + ... if sum(i)%5 == 0: + print(i, val) (0, 0, 0) 10 (1, 1, 3) 25 (2, 0, 3) 29 @@ -677,6 +691,8 @@ objects as inputs and returns an iterator that returns tuples providing each of the input sequence elements in the broadcasted result. +.. try_examples:: + >>> import numpy as np >>> for val in np.broadcast([[1, 0], [2, 3]], [0, 1]): ... print(val) diff --git a/doc/source/reference/arrays.datetime.rst b/doc/source/reference/arrays.datetime.rst index 2d10120c41f3..8dbff88c918e 100644 --- a/doc/source/reference/arrays.datetime.rst +++ b/doc/source/reference/arrays.datetime.rst @@ -57,6 +57,8 @@ letters, for a "Not A Time" value. .. admonition:: Example + .. try_examples:: + A simple ISO date: >>> import numpy as np @@ -95,6 +97,8 @@ datetime type with generic units. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.array(['2007-07-13', '2006-01-13', '2010-08-13'], dtype='datetime64') @@ -109,6 +113,8 @@ POSIX timestamps with the given unit. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.array([0, 1577836800], dtype='datetime64[s]') @@ -124,6 +130,8 @@ example :func:`arange` can be used to generate ranges of dates. .. admonition:: Example + .. try_examples:: + All the dates for one month: >>> import numpy as np @@ -146,6 +154,8 @@ because the moment of time is still being represented exactly. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.datetime64('2005') == np.datetime64('2005-01-01') @@ -175,6 +185,8 @@ data type also accepts the string "NAT" in place of the number for a "Not A Time .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.timedelta64(1, 'D') @@ -191,6 +203,8 @@ simple datetime calculations. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.datetime64('2009-01-01') - np.datetime64('2008-01-01') @@ -226,6 +240,8 @@ calculating the averaged values from the 400 year leap-year cycle. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.timedelta64(1, 'Y') @@ -307,6 +323,8 @@ specified in business days to datetimes with a unit of 'D' (day). .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.busday_offset('2011-06-23', 1) @@ -323,6 +341,8 @@ The rules most typically used are 'forward' and 'backward'. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.busday_offset('2011-06-25', 2) @@ -347,6 +367,8 @@ is necessary to get a desired answer. .. admonition:: Example + .. try_examples:: + The first business day on or after a date: >>> import numpy as np @@ -370,6 +392,8 @@ weekmask. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.busday_offset('2012-05', 1, roll='forward', weekmask='Sun') @@ -386,6 +410,8 @@ To test a `datetime64` value to see if it is a valid day, use :func:`is_busday`. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.is_busday(np.datetime64('2011-07-15')) # a Friday @@ -405,6 +431,8 @@ dates, use :func:`busday_count`: .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> np.busday_count(np.datetime64('2011-07-11'), np.datetime64('2011-07-18')) @@ -417,6 +445,8 @@ how many of them are valid dates, you can do this: .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18')) @@ -466,6 +496,8 @@ given below. 23:59:60.450 UTC" is a valid timestamp which is not parseable by `datetime64`: + .. try_examples:: + >>> import numpy as np >>> np.datetime64("2016-12-31 23:59:60.450") @@ -481,6 +513,8 @@ given below. Compute the number of SI seconds between "2021-01-01 12:56:23.423 UTC" and "2001-01-01 00:00:00.000 UTC": + .. try_examples:: + >>> import numpy as np >>> ( @@ -501,7 +535,8 @@ given below. where UT is `universal time `_: - + .. try_examples:: + >>> import numpy as np >>> a = np.datetime64("0000-01-01", "us") diff --git a/doc/source/reference/arrays.dtypes.rst b/doc/source/reference/arrays.dtypes.rst index 8aa7170df065..cad6679f07bf 100644 --- a/doc/source/reference/arrays.dtypes.rst +++ b/doc/source/reference/arrays.dtypes.rst @@ -68,6 +68,8 @@ Sub-arrays always have a C-contiguous memory layout. A simple data type containing a 32-bit big-endian integer: (see :ref:`arrays.dtypes.constructing` for details on construction) + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype('>i4') @@ -87,6 +89,8 @@ Sub-arrays always have a C-contiguous memory layout. A structured data type containing a 16-character string (in field 'name') and a sub-array of two 64-bit floating-point number (in field 'grades'): + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype([('name', np.str_, 16), ('grades', np.float64, (2,))]) @@ -98,6 +102,8 @@ Sub-arrays always have a C-contiguous memory layout. Items of an array of this data type are wrapped in an :ref:`array scalar ` type that also has two fields: + .. try_examples:: + >>> import numpy as np >>> x = np.array([('Sarah', (8.0, 7.0)), ('John', (6.0, 7.0))], dtype=dt) @@ -154,6 +160,8 @@ Array-scalar types .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype(np.int32) # 32-bit integer @@ -199,6 +207,8 @@ Built-in Python types .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype(float) # Python-compatible floating-point number @@ -229,6 +239,8 @@ One-character strings .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype('b') # byte, native byte order @@ -261,6 +273,8 @@ Array-protocol type strings (see :ref:`arrays.interface`) .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype('i4') # 32-bit signed integer @@ -294,6 +308,8 @@ String with comma-separated fields .. admonition:: Example + .. try_examples:: + - field named ``f0`` containing a 32-bit integer - field named ``f1`` containing a 2 x 3 sub-array of 64-bit floating-point numbers @@ -316,6 +332,8 @@ Type strings .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype('uint32') # 32-bit unsigned integer @@ -331,6 +349,8 @@ Type strings .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype((np.void, 10)) # 10-byte wide data block @@ -350,6 +370,8 @@ Type strings .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> dt = np.dtype((np.int32, (2,2))) # 2 x 2 integer sub-array @@ -384,6 +406,8 @@ Type strings .. admonition:: Example + .. try_examples:: + Data-type with fields ``big`` (big-endian 32-bit integer) and ``little`` (little-endian 32-bit integer): @@ -425,6 +449,8 @@ Type strings .. admonition:: Example + .. try_examples:: + Data type with fields ``r``, ``g``, ``b``, ``a``, each being an 8-bit unsigned integer: @@ -456,6 +482,8 @@ Type strings .. admonition:: Example + .. try_examples:: + Data type containing field ``col1`` (10-character string at byte position 0), ``col2`` (32-bit float at byte position 10), and ``col3`` (integers at byte position 14): @@ -481,6 +509,8 @@ Type strings .. admonition:: Example + .. try_examples:: + 32-bit integer, whose first two bytes are interpreted as an integer via field ``real``, and the following two bytes via field ``imag``. @@ -505,6 +535,8 @@ When checking for a specific data type, use ``==`` comparison. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.array([1, 2], dtype=np.float32) @@ -519,6 +551,8 @@ This equivalence can only be handled through ``==``, not through ``is``. .. admonition:: Example + .. try_examples:: + A :class:`dtype` object is equal to all data type specifications that are equivalent to it. @@ -540,6 +574,8 @@ Second, there is no guarantee that data type objects are singletons. .. admonition:: Example + .. try_examples:: + Do not use ``is`` because data type objects may or may not be singletons. >>> import numpy as np diff --git a/doc/source/reference/arrays.ndarray.rst b/doc/source/reference/arrays.ndarray.rst index 5e0c43438f03..5366a066d7b5 100644 --- a/doc/source/reference/arrays.ndarray.rst +++ b/doc/source/reference/arrays.ndarray.rst @@ -32,6 +32,8 @@ objects implementing the :class:`memoryview` or :ref:`array .. admonition:: Example + .. try_examples:: + A 2-dimensional array of size 2 x 3, composed of 4-byte integer elements: @@ -362,6 +364,8 @@ Many of these methods take an argument named *axis*. In such cases, .. admonition:: Example of the *axis* argument + .. try_examples:: + A 3-dimensional array of size 3 x 3 x 3, summed over each of its three axes: diff --git a/doc/source/reference/arrays.nditer.rst b/doc/source/reference/arrays.nditer.rst index 3c71a69e0fcd..add33f4a2b46 100644 --- a/doc/source/reference/arrays.nditer.rst +++ b/doc/source/reference/arrays.nditer.rst @@ -32,6 +32,8 @@ using the standard Python iterator interface. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -50,6 +52,8 @@ of that transpose in C order. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -80,6 +84,8 @@ order='C' for C order and order='F' for Fortran order. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -117,6 +123,8 @@ context is exited. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -158,6 +166,8 @@ elements each. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -186,6 +196,8 @@ progression of the index: .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -216,6 +228,8 @@ raise an exception. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.zeros((2,3)) @@ -236,6 +250,8 @@ produce identical results to the ones in the previous section. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -279,6 +295,8 @@ is enabled. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) @@ -323,6 +341,8 @@ data type doesn't match precisely. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) - 3 @@ -339,6 +359,8 @@ specified as an iterator flag. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6).reshape(2,3) - 3 @@ -364,6 +386,8 @@ complex to float. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6.) @@ -397,6 +421,8 @@ would violate the casting rule. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(6) @@ -422,6 +448,8 @@ a two dimensional array together. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(3) @@ -436,6 +464,8 @@ which includes the input shapes to help diagnose the problem. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(2) @@ -462,6 +492,8 @@ parameter support. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> def square(a): @@ -501,6 +533,8 @@ reasons. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> def square(a, out=None): @@ -559,6 +593,8 @@ Everything to do with the outer product is handled by the iterator setup. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(3) @@ -593,6 +629,8 @@ For a simple example, consider taking the sum of all elements in an array. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(24).reshape(2,3,4) @@ -614,6 +652,8 @@ sums along the last axis of `a`. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(24).reshape(2,3,4) @@ -650,6 +690,8 @@ buffering. .. admonition:: Example + .. try_examples:: + >>> import numpy as np >>> a = np.arange(24).reshape(2,3,4) diff --git a/doc/source/reference/arrays.scalars.rst b/doc/source/reference/arrays.scalars.rst index c80e3f932377..124ab572296f 100644 --- a/doc/source/reference/arrays.scalars.rst +++ b/doc/source/reference/arrays.scalars.rst @@ -191,6 +191,8 @@ Inexact types This means that variables with equal binary values but whose datatypes are of different precisions may display differently: + .. try_examples:: + >>> import numpy as np >>> f16 = np.float16("0.1") diff --git a/doc/source/reference/constants.rst b/doc/source/reference/constants.rst index 71ce0051bf13..79d758bddada 100644 --- a/doc/source/reference/constants.rst +++ b/doc/source/reference/constants.rst @@ -62,6 +62,8 @@ NumPy includes several constants: .. rubric:: Examples +.. try_examples:: + >>> import numpy as np >>> np.inf inf @@ -91,6 +93,8 @@ NumPy includes several constants: .. rubric:: Examples +.. try_examples:: + >>> import numpy as np >>> np.nan nan @@ -106,6 +110,8 @@ NumPy includes several constants: .. rubric:: Examples +.. try_examples:: + >>> import numpy as np >>> np.newaxis is None True diff --git a/doc/source/reference/maskedarray.baseclass.rst b/doc/source/reference/maskedarray.baseclass.rst index ed322852ba0a..a60b71c51fcb 100644 --- a/doc/source/reference/maskedarray.baseclass.rst +++ b/doc/source/reference/maskedarray.baseclass.rst @@ -15,6 +15,8 @@ defines several constants. specific entry of a masked array is masked, or to mask one or several entries of a masked array:: + .. try_examples:: + >>> import numpy as np >>> x = np.ma.array([1, 2, 3], mask=[0, 1, 0]) diff --git a/doc/source/reference/maskedarray.generic.rst b/doc/source/reference/maskedarray.generic.rst index 3324269ee7aa..9c44ebcbc589 100644 --- a/doc/source/reference/maskedarray.generic.rst +++ b/doc/source/reference/maskedarray.generic.rst @@ -35,6 +35,8 @@ masked (invalid). The package ensures that masked entries are not used in computations. +.. try_examples:: + As an illustration, let's consider the following dataset: >>> import numpy as np @@ -62,6 +64,8 @@ class, which is a subclass of :class:`numpy.ndarray`. The class, its attributes and methods are described in more details in the :ref:`MaskedArray class ` section. +.. try_examples:: + The :mod:`numpy.ma` module can be used as an addition to :mod:`numpy`: >>> import numpy as np @@ -108,6 +112,8 @@ There are several ways to construct a masked array. mask of the view is set to :attr:`nomask` if the array has no named fields, or an array of boolean with the same structure as the array otherwise. +.. try_examples:: + >>> import numpy as np >>> x = np.array([1, 2, 3]) >>> x.view(ma.MaskedArray) @@ -194,6 +200,8 @@ To retrieve only the valid entries, we can use the inverse of the mask as an index. The inverse of the mask can be calculated with the :func:`numpy.logical_not` function or simply with the ``~`` operator: +.. try_examples:: + >>> import numpy as np >>> x = ma.array([[1, 2], [3, 4]], mask=[[0, 1], [1, 0]]) >>> x[~x.mask] @@ -222,6 +230,8 @@ Masking an entry The recommended way to mark one or several specific entries of a masked array as invalid is to assign the special value :attr:`masked` to them: +.. try_examples:: + >>> x = ma.array([1, 2, 3]) >>> x[0] = ma.masked >>> x @@ -261,6 +271,8 @@ but this usage is discouraged. All the entries of an array can be masked at once by assigning ``True`` to the mask: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3], mask=[0, 0, 1]) >>> x.mask = True @@ -286,6 +298,8 @@ Unmasking an entry To unmask one or several specific entries, we can just assign one or several new valid values to them: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3], mask=[0, 0, 1]) >>> x @@ -307,6 +321,8 @@ new valid values to them: before the allocation. It can be re-hardened with :meth:`harden_mask` as follows: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3], mask=[0, 0, 1], hard_mask=True) >>> x @@ -337,6 +353,8 @@ To unmask all masked entries of a masked array (provided the mask isn't a hard mask), the simplest solution is to assign the constant :attr:`nomask` to the mask: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3], mask=[0, 0, 1]) >>> x @@ -361,6 +379,8 @@ output is either a scalar (if the corresponding entry of the mask is ``False``) or the special value :attr:`masked` (if the corresponding entry of the mask is ``True``): +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3], mask=[0, 0, 1]) >>> x[0] @@ -375,6 +395,8 @@ If the masked array has named fields, accessing a single entry returns a array with the same dtype as the initial array if at least one of the fields is masked. +.. try_examples:: + >>> import numpy.ma as ma >>> y = ma.masked_array([(1,2), (3, 4)], ... mask=[(0, 0), (0, 1)], @@ -391,6 +413,8 @@ mask is either :attr:`nomask` (if there was no invalid entries in the original array) or a view of the corresponding slice of the original mask. The view is required to ensure propagation of any modification of the mask to the original. +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1, 2, 3, 4, 5], mask=[0, 1, 0, 0, 1]) >>> mx = x[:3] @@ -430,6 +454,8 @@ ufuncs. Unary and binary functions that have a validity domain (such as :func:`~numpy.log` or :func:`~numpy.divide`) return the :data:`masked` constant whenever the input is masked or falls outside the validity domain: +.. try_examples:: + >>> import numpy.ma as ma >>> ma.log([-1, 0, 1, 2]) masked_array(data=[--, --, 0.0, 0.6931471805599453], @@ -444,6 +470,8 @@ the name of the ufunc, its arguments and its domain), the context is processed and entries of the output masked array are masked wherever the corresponding input fall outside the validity domain: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([-1, 1, 0, 2, 3], mask=[0, 0, 0, 0, 1]) >>> np.log(x) @@ -462,6 +490,8 @@ Let's consider a list of elements, ``x``, where values of -9999. represent missing data. We wish to compute the average value of the data and the vector of anomalies (deviations from the average): +.. try_examples:: + >>> import numpy.ma as ma >>> x = [0.,1.,-9999.,3.,4.] >>> mx = ma.masked_values (x, -9999.) @@ -479,6 +509,8 @@ Filling in the missing data Suppose now that we wish to print that same data, but with the missing values replaced by the average value. +.. try_examples:: + >>> import numpy.ma as ma >>> mx = ma.masked_values (x, -9999.) >>> print(mx.filled(mx.mean())) @@ -491,6 +523,8 @@ Numerical operations Numerical operations can be easily performed without worrying about missing values, dividing by zero, square roots of negative numbers, etc.:: +.. try_examples:: + >>> import numpy.ma as ma >>> x = ma.array([1., -1., 3., 4., 5., 6.], mask=[0,0,0,0,1,0]) >>> y = ma.array([1., 2., 0., 4., 5., 6.], mask=[0,0,0,0,0,1]) @@ -509,6 +543,8 @@ Let's consider an array ``d`` of floats between 0 and 1. We wish to compute the average of the values of ``d`` while ignoring any data outside the range ``[0.2, 0.9]``: +.. try_examples:: + >>> import numpy as np >>> import numpy.ma as ma >>> d = np.linspace(0, 1, 20) diff --git a/doc/source/reference/random/generator.rst b/doc/source/reference/random/generator.rst index 088d159c74f5..953cf9b3845e 100644 --- a/doc/source/reference/random/generator.rst +++ b/doc/source/reference/random/generator.rst @@ -72,6 +72,8 @@ By default, `Generator.permuted` returns a copy. To operate in-place with `Generator.permuted`, pass the same array as the first argument *and* as the value of the ``out`` parameter. For example, +.. try_examples:: + >>> import numpy as np >>> rng = np.random.default_rng() >>> x = np.arange(0, 15).reshape(3, 5) @@ -101,6 +103,8 @@ which dimension of the input array to use as the sequence. In the case of a two-dimensional array, ``axis=0`` will, in effect, rearrange the rows of the array, and ``axis=1`` will rearrange the columns. For example +.. try_examples:: + >>> import numpy as np >>> rng = np.random.default_rng() >>> x = np.arange(0, 15).reshape(3, 5) @@ -121,6 +125,8 @@ how `numpy.sort` treats it. Each slice along the given axis is shuffled independently of the others. Compare the following example of the use of `Generator.permuted` to the above example of `Generator.permutation`: +.. try_examples:: + >>> import numpy as np >>> rng = np.random.default_rng() >>> rng.permuted(x, axis=1) #doctest: +SKIP @@ -137,6 +143,8 @@ Shuffling non-NumPy sequences `Generator.shuffle` works on non-NumPy sequences. That is, if it is given a sequence that is not a NumPy array, it shuffles that sequence in-place. +.. try_examples:: + >>> import numpy as np >>> rng = np.random.default_rng() >>> a = ['A', 'B', 'C', 'D', 'E'] diff --git a/doc/source/reference/random/index.rst b/doc/source/reference/random/index.rst index 278b78269279..2df350c5d64e 100644 --- a/doc/source/reference/random/index.rst +++ b/doc/source/reference/random/index.rst @@ -18,16 +18,24 @@ probability distributions. In general, users will create a `Generator` instance with `default_rng` and call the various methods on it to obtain samples from different distributions. +.. try_examples:: + >>> import numpy as np >>> rng = np.random.default_rng() - # Generate one random float uniformly distributed over the range [0, 1) + + Generate one random float uniformly distributed over the range [0, 1) + >>> rng.random() #doctest: +SKIP 0.06369197489564249 # may vary - # Generate an array of 10 numbers according to a unit Gaussian distribution + + Generate an array of 10 numbers according to a unit Gaussian distribution + >>> rng.standard_normal(10) #doctest: +SKIP array([-0.31018314, -1.8922078 , -0.3628523 , -0.63526532, 0.43181166, # may vary 0.51640373, 1.25693945, 0.07779185, 0.84090247, -2.13406828]) - # Generate an array of 5 integers uniformly over the range [0, 10) + + Generate an array of 5 integers uniformly over the range [0, 10) + >>> rng.integers(low=0, high=10, size=5) #doctest: +SKIP array([8, 7, 6, 2, 0]) # may vary @@ -38,6 +46,8 @@ generate different numbers each time. The pseudo-random sequences will be independent for all practical purposes, at least those purposes for which our pseudo-randomness was good for in the first place. +.. try_examples:: + >>> import numpy as np >>> rng1 = np.random.default_rng() >>> rng1.random() #doctest: +SKIP @@ -63,6 +73,9 @@ intentionally *trying* to reproduce their result. A convenient way to get such a seed number is to use :py:func:`secrets.randbits` to get an arbitrary 128-bit integer. + +.. try_examples:: + >>> import numpy as np >>> import secrets >>> secrets.randbits(128) #doctest: +SKIP diff --git a/doc/source/reference/routines.char.rst b/doc/source/reference/routines.char.rst index 7a8728f2d727..92c605071e50 100644 --- a/doc/source/reference/routines.char.rst +++ b/doc/source/reference/routines.char.rst @@ -16,6 +16,8 @@ Legacy fixed-width string functionality The `numpy.char` module provides a set of vectorized string operations for arrays of type `numpy.str_` or `numpy.bytes_`. For example +.. try_examples:: + >>> import numpy as np >>> np.char.capitalize(["python", "numpy"]) array(['Python', 'Numpy'], dtype='>> import numpy as np >>> p1d = np.poly1d([1, 2, 3]) diff --git a/doc/source/reference/routines.rec.rst b/doc/source/reference/routines.rec.rst index aa3a715f47a9..c8c12cc31cef 100644 --- a/doc/source/reference/routines.rec.rst +++ b/doc/source/reference/routines.rec.rst @@ -13,6 +13,8 @@ Most commonly, ndarrays contain elements of a single type, e.g. floats, integers, bools etc. However, it is possible for elements to be combinations of these using structured types, such as: +.. try_examples:: + >>> import numpy as np >>> a = np.array([(1, 2.0), (1, 2.0)], ... dtype=[('x', np.int64), ('y', np.float64)]) diff --git a/doc/source/reference/routines.rst b/doc/source/reference/routines.rst index e4dabd0e60a0..df60405f8030 100644 --- a/doc/source/reference/routines.rst +++ b/doc/source/reference/routines.rst @@ -4,11 +4,9 @@ Routines and objects by topic ***************************** -In this chapter routine docstrings are presented, grouped by functionality. +In this chapter, routine docstrings are presented, grouped by functionality. Many docstrings contain example code, which demonstrates basic usage -of the routine. The examples assume that NumPy is imported with:: - - >>> import numpy as np +of the routine. A convenient way to execute examples is the ``%doctest_mode`` mode of IPython, which allows for pasting of multi-line examples and preserves diff --git a/doc/source/reference/routines.strings.rst b/doc/source/reference/routines.strings.rst index 70f98e417b06..68387aee22ff 100644 --- a/doc/source/reference/routines.strings.rst +++ b/doc/source/reference/routines.strings.rst @@ -9,10 +9,12 @@ String functionality The `numpy.strings` module provides a set of universal functions operating on arrays of type `numpy.str_` or `numpy.bytes_`. -For example +For example, - >>> np.strings.add(["num", "doc"], ["py", "umentation"]) - array(['numpy', 'documentation'], dtype='>> np.strings.add(["num", "doc"], ["py", "umentation"]) + array(['numpy', 'documentation'], dtype='>>`` and ``...`` are not part of the code and may cause an error if entered at a Python prompt. +To run the code in the examples, you can copy and paste it into a Python script or +REPL, or use the experimental interactive examples in the browser provided in various +locations in the documentation. + Why use NumPy? -------------- diff --git a/environment.yml b/environment.yml index 93dc61953a59..d6256d02ec33 100644 --- a/environment.yml +++ b/environment.yml @@ -39,6 +39,9 @@ dependencies: - pydata-sphinx-theme>=0.15.2 - doxygen - towncrier + - jupyterlite-sphinx>=0.18.0 + # see https://github.com/jupyterlite/pyodide-kernel#compatibility + - jupyterlite-pyodide-kernel==0.5.2 # supports Pyodide 0.27.1 # NOTE: breathe 4.33.0 collides with sphinx.ext.graphviz - breathe>4.33.0 # For linting diff --git a/numpy/_core/code_generators/ufunc_docstrings.py b/numpy/_core/code_generators/ufunc_docstrings.py index 0b4f84fb8b4b..2f914740562d 100644 --- a/numpy/_core/code_generators/ufunc_docstrings.py +++ b/numpy/_core/code_generators/ufunc_docstrings.py @@ -426,10 +426,10 @@ def add_newdoc(place, name, doc): Examples -------- + We expect the arctan of 0 to be 0, and of 1 to be pi/4: >>> import numpy as np - >>> np.arctan([0, 1]) array([ 0. , 0.78539816]) @@ -507,10 +507,10 @@ def add_newdoc(place, name, doc): Examples -------- + Consider four points in different quadrants: >>> import numpy as np - >>> x = np.array([-1, +1, +1, -1]) >>> y = np.array([-1, -1, +1, +1]) >>> np.arctan2(y, x) * 180 / np.pi @@ -989,7 +989,6 @@ def add_newdoc(place, name, doc): Convert a radian array to degrees >>> import numpy as np - >>> rad = np.arange(12.)*np.pi/6 >>> np.degrees(rad) array([ 0., 30., 60., 90., 120., 150., 180., 210., 240., @@ -1224,6 +1223,7 @@ def add_newdoc(place, name, doc): >>> import numpy as np >>> import matplotlib.pyplot as plt + >>> import numpy as np >>> x = np.linspace(-2*np.pi, 2*np.pi, 100) >>> xx = x + 1j * x[:, np.newaxis] # a + ib over complex plane @@ -1298,12 +1298,12 @@ def add_newdoc(place, name, doc): Examples -------- + The true value of ``exp(1e-10) - 1`` is ``1.00000000005e-10`` to about 32 significant digits. This example shows the superiority of expm1 in this case. >>> import numpy as np - >>> np.expm1(1e-10) 1.00000000005e-10 >>> np.exp(1e-10) - 1 @@ -2839,7 +2839,6 @@ def add_newdoc(place, name, doc): For 2-D arrays it is the matrix product: >>> import numpy as np - >>> a = np.array([[1, 0], ... [0, 1]]) >>> b = np.array([[4, 1], diff --git a/numpy/lib/_polynomial_impl.py b/numpy/lib/_polynomial_impl.py index 420e357eb354..535e21af20a5 100644 --- a/numpy/lib/_polynomial_impl.py +++ b/numpy/lib/_polynomial_impl.py @@ -101,6 +101,7 @@ def poly(seq_of_zeros): Examples -------- + Given a sequence of a polynomial's zeros: >>> import numpy as np @@ -298,6 +299,7 @@ def polyint(p, m=1, k=None): Examples -------- + The defining property of the antiderivative: >>> import numpy as np @@ -395,6 +397,7 @@ def polyder(p, m=1): Examples -------- + The derivative of the polynomial :math:`x^3 + x^2 + x^1 + 1` is: >>> import numpy as np @@ -883,6 +886,7 @@ def polysub(a1, a2): Examples -------- + .. math:: (2 x^2 + 10 x - 2) - (3 x^2 + 10 x -4) = (-x^2 + 2) >>> import numpy as np @@ -1020,9 +1024,11 @@ def polydiv(u, v): Examples -------- + .. math:: \\frac{3x^2 + 5x + 2}{2x + 1} = 1.5x + 1.75, remainder 0.25 >>> import numpy as np + >>> x = np.array([3.0, 5.0, 2.0]) >>> y = np.array([2.0, 1.0]) >>> np.polydiv(x, y) @@ -1111,6 +1117,8 @@ class poly1d: Examples -------- + >>> import numpy as np + Construct the polynomial :math:`x^2 + 2x + 3`: >>> import numpy as np diff --git a/numpy/linalg/_linalg.py b/numpy/linalg/_linalg.py index e111a734ce6b..32a52b7b8cdb 100644 --- a/numpy/linalg/_linalg.py +++ b/numpy/linalg/_linalg.py @@ -1294,8 +1294,9 @@ def eigvalsh(a, UPLO='L'): [0.+2.j, 2.+0.j]]) >>> wa = LA.eigvalsh(a) >>> wb = LA.eigvals(b) - >>> wa; wb + >>> wa array([1., 6.]) + >>> wb array([6.+0.j, 1.+0.j]) """ diff --git a/requirements/doc_requirements.txt b/requirements/doc_requirements.txt index e72ad3697ec6..330f0f7ac8b9 100644 --- a/requirements/doc_requirements.txt +++ b/requirements/doc_requirements.txt @@ -17,5 +17,12 @@ pickleshare towncrier toml + # for doctests, also needs pytz which is in test_requirements scipy-doctest==1.6.0 + +# interactive documentation utilities +# see https://github.com/jupyterlite/pyodide-kernel#compatibility +jupyterlite-sphinx>=0.18.0 +# Works with Pyodide 0.27.1 +jupyterlite-pyodide-kernel==0.5.2