Skip to content

Commit f90bf15

Browse files
committed
Updates to arithmetic docs.
1 parent 237c90a commit f90bf15

File tree

1 file changed

+53
-35
lines changed

1 file changed

+53
-35
lines changed

docs/explaining_ndcube/arithmetic.rst

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ Arithmetic Operations
66

77
Arithmetic operations are a crucial tool in n-dimensional data analysis.
88
Applications include subtracting a background from a 1-D timeseries or spectrum, scaling an image by a vignetting function, and many others.
9-
To aid with such workflows, `~ndcube.NDCube` supports addition, subtraction, multiplication, and division with numbers, arrays, `~astropy.units.Quantity`.
9+
To aid with such workflows, `~ndcube.NDCube` supports addition, subtraction, multiplication, and division with numbers, arrays, and `~astropy.units.Quantity`.
1010
Raising an `~ndcube.NDCube` to a power is also supported.
1111
These operations return a new `~ndcube.NDCube` with the data array (and, where appropriate, the uncertainties) altered in accordance with the arithmetic operation.
1212
Other attributes of the `~ndcube.NDCube` remain unchanged.
1313

14-
In addition, combining `~ndcube.NDCube` with coordinate-less `~astropy.nddata.NDData` subclasses via these operations is also supported.
15-
Such operations can be more complicated. See the section below on :ref:`arithmetic_nddata` for a discussion separate, more detailed discussion.
14+
In addition, arithmetic operations between `~ndcube.NDCube` and coordinate-less `~astropy.nddata.NDData` subclasses is supported.
15+
See the section below on :ref:`arithmetic_nddata` for a separate, more detailed discussion.
1616

1717
.. _arithmetic_standard:
1818

19-
Standard Arithmetic Operations
20-
==============================
19+
Arithmetic Operations with Numbers, Arrays and Quantities
20+
=========================================================
2121

22-
Addition and Subtraction with Numbers, Arrays and Quantities
23-
------------------------------------------------------------
22+
Addition and Subtraction
23+
------------------------
2424

2525
Numbers, arrays and `~astropy.units.Quantity` can be added to and subtracted from `~ndcube.NDCube` via the ``+`` and ``-`` operators.
26-
Note that addition and subtraction only change the data values of the `~ndcube.NDCube`.
27-
Let's deomonstrate with an example `~ndcube.NDCube`, ``cube``
26+
Note that these only change the data values of the `~ndcube.NDCube` and units must be consistent with that of the `~ndcube.NDCube`.
27+
Let's demonstrate with an example `~ndcube.NDCube`, ``cube``
2828

2929
.. expanding-code-block:: python
3030
:summary: Expand to see cube instantiated.
@@ -70,7 +70,7 @@ Let's deomonstrate with an example `~ndcube.NDCube`, ``cube``
7070
[14, 15, 16]])
7171
7272
Note that all the data values have been increased by 1.
73-
We can also add an array if we want to add a different number to each data element:
73+
We can also use an array if we want to add a different number to each data element:
7474

7575
.. code-block:: python
7676
@@ -99,7 +99,7 @@ Subtraction works in the same way.
9999
array([[10, 10, 10],
100100
[10, 10, 10]])
101101
102-
Note that int he above examples, ``cube`` has no unit.
102+
Note that in the above examples, ``cube`` has no unit.
103103
This is why we are able to add and subtract numbers and arrays.
104104
If, however, we have an `~ndcube.NDCube` with a unit assigned,
105105

@@ -136,16 +136,16 @@ In such cases, we must use a `~astropy.units.Quantity` with a compatible unit:
136136
array([[10., 10., 10.],
137137
[10., 10., 10.]])
138138
139-
Multiplying and Dividing with Numbers, Arrays and Quantities
140-
------------------------------------------------------------
139+
Multiplication and Division
140+
---------------------------
141141

142142
An `~ndcube.NDCube` can be multiplied and divided by numbers, arrays, and `~astropy.units.Quantity` via the ``*`` and ``-`` operators.
143143
These work similarly to addition and subtraction with a few minor differences:
144144

145145
- The uncertainties of the resulting `~ndcube.NDCube` are scaled by the same factor as the data.
146-
- Classes with different units can be combined.
146+
- Classes with different non-equivalent units can be combined.
147147

148-
* e.g. an `~ndcube.NDCube` with a unit of counts divided by an `~astropy.units.Quantity` with a unit is seconds will result in an `~ndcube.NDCube` with a unit of counts per second.
148+
* e.g. an `~ndcube.NDCube` with a unit of ``ct`` divided by an `~astropy.units.Quantity` with a unit of ``s`` will result in an `~ndcube.NDCube` with a unit of ``ct / s``.
149149
* This also holds for cases were unitful and unitless classes can be combined. In such cases, the unit of the resulting `~ndcube.NDCube` will be the same as that of the unitful object.
150150

151151
Below are some examples.
@@ -180,7 +180,7 @@ Below are some examples.
180180
StdDevUncertainty([[1. , 2.2, 3.6],
181181
[5.2, 7. , 9. ]])
182182
183-
>>> # Divide by an astropy Quantity.
183+
>>> # Divide by a scalar astropy Quantity.
184184
>>> new_cube = cube_with_unit / (2 * u.s)
185185
186186
>>> # Inspect attributes of resultant cube.
@@ -193,14 +193,22 @@ Below are some examples.
193193
StdDevUncertainty([[0.5 , 0.55, 0.6 ],
194194
[0.65, 0.7 , 0.75]])
195195
196-
Note that when performing arithmetic operations with `~ndcube.NDCube` and array-like objects, their shapes only have to be broadcastable.
196+
Note that when performing arithmetic operations with `~ndcube.NDCube` and array-like objects, their shapes only have to be broadcastable, not necessarily the same.
197197
For example:
198198

199+
.. code-block:: python
200+
201+
>>> arr[0]
202+
array([1, 2, 3])
203+
204+
>>> new_cube = cube + arr[0]
205+
>>> new_cube.data
206+
array([[11, 13, 15],
207+
[14, 16, 18]])
208+
199209
Raising NDCube to a Power
200210
-------------------------
201211

202-
`~ndcube.NDCube` can be raised to a power.
203-
204212
.. code-block:: python
205213
206214
>>> cube_with_unit.data
@@ -228,24 +236,33 @@ Arithmetic Operations with Coordinate-less NDData
228236
=================================================
229237

230238
Sometimes more advanced arithmetic operations are required.
231-
For example, we may want to create a sequence of running difference images which highlight changes between frames, and propagate the uncertainties associated with each image.
239+
For example, we may want to create a sequence of running difference images which highlight changes between frames, and propagate the uncertainties associated with the image subtraction.
232240
Alternatively, we may want to subtract one image from another, but exclude a certain region of the image with a mask.
233-
In such cases, numbers, arrays and `~astropy.units.Quantity` are insufficient, and we would like to subtract two `~ndcube.NDCube` objects.
234-
This is not directly supported, but can still be achieved in practice, as we shall see below.
241+
In such cases, numbers, arrays and `~astropy.units.Quantity` are insufficient.
242+
Instead it would be better to subtract two `~ndcube.NDCube` objects.
243+
This is not directly supported, for reasons we will see below.
244+
The the effect of the operation can still be achieved in practice, as well shall also see.
235245

236246
Why Arithmetic Operations with Coordinate-aware NDData Are Not Directly Supported, and How This Can Be Overcome
237247
---------------------------------------------------------------------------------------------------------------
238248

239-
Arithmetic operations between two `~ndcube.NDCube` instances (or equivalently, an `~ndcube.NDCube` and another coordinate-aware `~astropy.nddata.NDData` subclass) are not supported because of the possibility of supporting non-sensical operations.
240-
For example, what does it mean to multiply a spectrum and an image in a coordinate-aware way?
249+
The remit of the `ndcube` package is support N-dimensional coordinate-aware data astronomical data analysis.
250+
Arithmetic operations between two `~ndcube.NDCube` instances (or equivalently, an `~ndcube.NDCube` and another coordinate-aware `~astropy.nddata.NDData` subclass) are therefore not directly supported because of the possibility of supporting non-sensical operations.
251+
(Although they are supported indirectly, as we shall see below.)
252+
For example, what does it mean to multiply a spectrum and an image?
241253
Getting the difference between two images may make physical sense, but only in certain circumstances.
242254
For example, subtracting two sequential images of the same region of the Sun is a common step in many solar image analysis workflows.
243-
However, subtracting images of different parts of the sky, e.g. the Sun and the Crab Nebula, does not result in a physically meaningful image.
255+
But subtracting images of different parts of the sky, e.g. the Sun and the Crab Nebula, does not produce a physically meaningful result.
244256
Even when subtracting two images of the Sun, drift in the telescope's pointing may result in the pixels in each image corresponding to different points in the Sun.
245-
In this case, it is questionable whether this operation makes physical sense after all.
257+
In this case, it is questionable whether even this operation makes physical sense.
246258
Moreover, in all of these cases, it is not at all clear what the resulting WCS object should be.
247259

248-
In many cases, a simple solution would be to extract the data (an optionally the unit) from one of the `~ndcube.NDCube` instances and perform the operation as described in the above section on :ref:`arithmetic_standard`:
260+
One way to ensure physically meaningful, coordinate-aware arithmetic operations between `~ndcube-NDCube` instances would be to compare their WCS objects are the same within a certain tolerance.
261+
Alternatively, the arithmetic operation could attempt to reproject on `~ndcube-NDCube` to the other's WCS.
262+
However, these operations are potentially prohibitively slow and operationally expensive.
263+
264+
Despite this, arithmetic operations between two `~ndcube.NDCube` instance is supported, provided the coordinate-awareness of one is dropped.
265+
A simple solution that satisfies many use-cases is to extract the data (an optionally the unit) from one of the `~ndcube.NDCube` instances and perform the operation as described in the above section on :ref:`arithmetic_standard`:
249266

250267
.. expanding-code-block:: python
251268
:summary: Expand to see definition of cube1 and cube2.
@@ -258,8 +275,8 @@ In many cases, a simple solution would be to extract the data (an optionally the
258275
>>> new_cube = cube1 - cube2.data * cube2.unit
259276
260277
However, this does not allow for the propagation of uncertainties or masks associated with ``cube2``.
261-
Therefore, `~ndcube.NDCube` does support arithmetic operations with instances of `~astropy.nddata.NDData` subclasses whose ``wcs`` attribute is ``None``.
262-
This makes users explicitly aware that they are dispensing with coordinate-awareness on one of their operands.
278+
Therefore, `~ndcube.NDCube` also support arithmetic operations with instances of `~astropy.nddata.NDData` subclasses whose ``wcs`` attribute is ``None``.
279+
Requiring users to remove coordinates in this way makes them explicitly aware that they are dispensing with coordinate-awareness on one of their operands.
263280
It also leaves only one WCS involved in the operation, thus removing ambiguity regarding the WCS of the `~ndcube.NDCube` resulting from the operation.
264281

265282
Users who would like to drop coordinate-awareness from an `~ndcube.NDCube` can so simply by converting it to an `~astropy.nddata.NDData` and setting the ``wcs`` to ``None``:
@@ -281,7 +298,7 @@ The power of using coordinate-less `~astropy.nddata.NDData` classes is the abili
281298
Uncertainty Propagation
282299
***********************
283300

284-
The uncertainty associated with the `~ndcube.NDCube` resulting from the arithmetic operation depends on the uncertainty types of the operands:
301+
The uncertainty resulting from the arithmetic operation depends on the uncertainty types of the operands:
285302

286303
- ``NDCube.uncertainty`` and ``NDData.uncertainty`` are both ``None`` => ``new_cube.uncertainty`` is ``None``;
287304
- ``NDCube`` or ``NDData`` have uncertainty, but not both => the existing uncertainty is assigned to ``new_cube`` as is;
@@ -294,7 +311,7 @@ If users would like to remove uncertainty from one of the operands in order to p
294311
.. code-block:: python
295312
296313
>>> # Remove uncertainty from NDCube
297-
>>> cube1_nouncert = NDCube(cube2, wcs=None)
314+
>>> cube1_nouncert = NDCube(cube1, wcs=None)
298315
>>> new_cube = cube1_nouncert + cube2_nocoords
299316
300317
>>> # Remove uncertainty from coordinate-less NDData
@@ -320,22 +337,23 @@ In the case of addition and subtraction, the ``fill_value`` should be ``0``.
320337
>>> cube_filled = cube1.fill_masked(0)
321338
>>> new_cube = cube_filled + cube2_nocoords
322339
323-
By replacing masked data values with ``0``, these pixels are effectively not included in the addition, and the data values from ``cube2_nocoords`` are passed into ``new_cube`` unchanged.
340+
By replacing masked data values with ``0``, these values are effectively not included in the addition, and the data values from ``cube2_nocoords`` are passed into ``new_cube`` unchanged.
324341
In the above example, both operands have uncertainties, which means masked uncertainties are propagated through the addition, even though the masked data values have been set to ``0``.
325342
Propagation of masked uncertainties can also be suppressed by setting the optional kwarg, ``fill_uncertainty_value=0``.
343+
326344
By default, the mask of ``cube_filled`` is not changed, and therefore is incorporated into the mask of the output cube.
327345
However, mask propagation can also be suppressed by setting the optional kwarg, ``unmask=True``, which sets ``cube_filled0.mask`` to ``False``.
328346

329-
In the case of multiplication and division, and ``fill_value`` of ``1`` will prevent masked values being including in the operations:
347+
In the case of multiplication and division, a ``fill_value`` of ``1`` will prevent masked values being including in the operations. (Also see the optional use of the ``fill_uncertainty_value`` and ``unmask`` kwargs.)
330348

331349
.. code-block:: python
332350
333351
>>> cube_filled = cube1.fill_masked(1, fill_uncertainty_value=0, unmask=True)
334352
>>> new_cube = cube_filled * cube2_nocoords
335353
336354
By default, `ndcube.NDCube.fill_masked` returns a new `~ndcube.NDCube` instance.
337-
However, in some case it may be preferable to fill the masked values in-place, e.g. because the data within the `~ndcube.NDCube` is very large and users want to control the number of copies in RAM.
338-
In this case, the ``fill_in_place`` can be used.
355+
However, in some case it may be preferable to fill the masked values in-place, for example because the data within the `~ndcube.NDCube` is very large and users want to control the number of copies in RAM.
356+
In this case, the ``fill_in_place`` kwarg can be used.
339357

340358
.. code-block:: python
341359

0 commit comments

Comments
 (0)