.. _neuronlist_math:
NeuronList math
***************
:class:`~pymaid.CatmaidNeuronList` implement some of the basic arithmetic and comparison operators that you might know from standard ``lists`` or ``numpy.arrays``. Most this should be fairly intuitive (I hope) but there are a few things you should be aware of. The following examples will illustrate that:
As per usual, we need to get some neurons first (I'm assuming you've already imported and setup pymaid):
.. code:: ipython3
nl = pymaid.get_neurons('annotation:glomerulus DA1 right')
nl.head()
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57316 ML 2863105 |
2863104 |
5222 |
439 |
149 |
157 |
63 |
1109.886700 |
NA |
True |
| 1 |
PN glomerulus DA1 57382 ML |
57381 |
7730 |
360 |
153 |
162 |
71 |
1215.920600 |
NA |
True |
| 2 |
PN glomerulus DA1 61222 AJ |
61221 |
7875 |
534 |
135 |
140 |
26 |
1182.642823 |
NA |
True |
| 3 |
PN glomerulus DA1 57354 GA |
57353 |
4898 |
327 |
90 |
95 |
52 |
1113.156682 |
NA |
True |
| 4 |
PN glomerulus DA1 57324 LK JSL |
57323 |
4585 |
438 |
120 |
127 |
59 |
1035.099284 |
NA |
True |
Comparisons
-----------
The ``==`` operator compares two elements:
.. code:: ipython3
1 == 1
.. parsed-literal::
True
.. code:: ipython3
2 == 1
.. parsed-literal::
False
For :class:`~pymaid.CatmaidNeuron` this is done by comparing the neurons' morphologies (somas, root nodes, cable length, ...) and meta data (skeleton ID, name, ...).
.. code:: ipython3
n1 = nl[0]
n2 = nl[0]
n1 == n2
.. parsed-literal::
True
.. code:: ipython3
n1 = nl[0]
n2 = nl[1]
n1 == n2
.. parsed-literal::
False
For :class:`~pymaid.CatmaidNeuronList`, we do the same comparison pairwise between neurons in both neuronlists - so order matters!.
.. code:: ipython3
nl == nl
.. parsed-literal::
True
.. code:: ipython3
nl == nl[:-1]
.. parsed-literal::
False
.. code:: ipython3
nl[[0, 1, 2]] == nl[[2, 1, 0]]
.. parsed-literal::
False
This is safe against copying but making any changes to the neurons will cause inequality:
.. code:: ipython3
n1 = nl[0]
n2 = n1.copy()
n1 == n2
.. parsed-literal::
True
.. code:: ipython3
n1 = nl[0]
n2 = n1.reroot(nl[0].tags['ends'][0], inplace=False)
n1 == n2
.. parsed-literal::
False
You can also ask if a neuron is in a given ``CatmaidNeuronList``:
.. code:: ipython3
n1 = nl[0]
n1 in nl
.. parsed-literal::
True
.. code:: ipython3
n1 in nl[1:]
.. parsed-literal::
False
This also works with skeleton IDs
.. code:: ipython3
n1.skeleton_id in nl
.. parsed-literal::
True
.. code:: ipython3
n1.skeleton_id in nl[1:]
.. parsed-literal::
False
Addition
--------
To merge two lists in Python, you can simply add them:
.. code:: ipython3
a = [1]
b = [3]
a + b
.. parsed-literal::
[1, 3]
:class:`~pymaid.CatmaidNeuronList` works exactly the same:
.. code:: ipython3
a = nl[0]
b = nl[[1, 2]]
a + b
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57382 ML |
57381 |
7729 |
360 |
153 |
162 |
71 |
1215.920599 |
NA |
True |
| 1 |
AL.L(DA1) -{mALT}-> CAL.L-LH.L 2379518 PN022 D... |
2379517 |
4657 |
411 |
86 |
90 |
31 |
836.067253 |
NA |
True |
| 2 |
PN glomerulus DA1 57312 LK |
57311 |
4882 |
429 |
157 |
164 |
105 |
1182.102458 |
NA |
True |
This also works on with two single :class:`~pymaid.CatmaidNeuron`! You can use that to combine them into a list:
.. code:: ipython3
a = nl[0]
b = nl[1]
a + b
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57316 ML 2863105 |
2863104 |
5222 |
439 |
149 |
157 |
63 |
1109.88670 |
NA |
True |
| 1 |
AL.L(DA1) -{mALT}-> CAL.L-LH.L 2319458 PN036 D... |
2319457 |
10209 |
964 |
431 |
458 |
90 |
1747.51171 |
NA |
True |
.. note::
If you want to combine two CatmaidNeurons into a single neuron instead of creating a neuronlist, check out :func:`pymaid.stitch_neurons`.
Substraction
------------
To remove an item from a Python list, you would call the ``.pop()`` method:
.. code:: ipython3
a = [1, 2, 3]
b = 2
a.pop(b)
a
.. parsed-literal::
[1, 2]
:class:`~pymaid.CatmaidNeuronList` lets you simply use substraction:
.. code:: ipython3
a = nl[[0, 1, 2]]
b = nl[2]
a - b
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57382 ML |
57381 |
7729 |
360 |
153 |
162 |
71 |
1215.920599 |
NA |
True |
| 1 |
AL.L(DA1) -{mALT}-> CAL.L-LH.L 2379518 PN022 D... |
2379517 |
4657 |
411 |
86 |
90 |
31 |
836.067253 |
NA |
True |
Bitwise AND
-----------
To find the intersection between two lists, you would use ``sets`` and the ``&`` operator:
.. code:: ipython3
a = set([0, 1, 2])
b = set([2, 3, 4])
a & b
.. parsed-literal::
{2}
:class:`~pymaid.CatmaidNeuronList` work similarly:
.. code:: ipython3
a = nl[[0, 1, 2]]
b = nl[[2, 3, 4]]
a & b
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57312 LK |
57311 |
4882 |
429 |
157 |
164 |
105 |
1182.102458 |
NA |
True |
All of the above also work with skeleton IDs as one of the operators
.. code:: ipython3
a = nl[[0, 1, 2]]
b = nl[[2, 3, 4]]
a & b.skeleton_id
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57312 LK |
57311 |
4882 |
429 |
157 |
164 |
105 |
1182.102458 |
NA |
True |
Multiplication and Division
---------------------------
So far, all operations have led to changes in the structure of the :class:`~pymaid.CatmaidNeuronList`. Multiplication and division are **different**! If you multiply/divide a :class:`~pymaid.CatmaidNeuron` or :class:`~pymaid.CatmaidNeuronList` by a number, you will change the *coordinates* of nodes and connectors (plus node radii):
.. code:: ipython3
n = nl[0]
n.nodes.head()
.. raw:: html
|
treenode_id |
parent_id |
creator_id |
x |
y |
z |
radius |
confidence |
type |
| 0 |
3046710 |
32963981 |
53 |
442883 |
212802 |
44240 |
-1 |
5 |
slab |
| 1 |
32963981 |
3046707 |
150 |
442931 |
212892 |
44040 |
-1 |
5 |
slab |
| 2 |
652935 |
652934 |
22 |
444126 |
151826 |
216240 |
-1 |
5 |
slab |
| 3 |
3245741 |
3245737 |
61 |
414617 |
243177 |
52400 |
3451 |
5 |
end |
| 4 |
3042776 |
27298300 |
53 |
436753 |
225519 |
38000 |
-1 |
5 |
slab |
.. code:: ipython3
n2 = n / 1000
n2.nodes.head()
.. raw:: html
|
treenode_id |
parent_id |
creator_id |
x |
y |
z |
radius |
confidence |
type |
| 0 |
3046710 |
32963981 |
53 |
442.883 |
212.802 |
44.24 |
-0.001 |
5 |
slab |
| 1 |
32963981 |
3046707 |
150 |
442.931 |
212.892 |
44.04 |
-0.001 |
5 |
slab |
| 2 |
652935 |
652934 |
22 |
444.126 |
151.826 |
216.24 |
-0.001 |
5 |
slab |
| 3 |
3245741 |
3245737 |
61 |
414.617 |
243.177 |
52.40 |
3.451 |
5 |
end |
| 4 |
3042776 |
27298300 |
53 |
436.753 |
225.519 |
38.00 |
-0.001 |
5 |
slab |
The main use of this is to convert units from e.g. nm to um.
Dealing with unequal neuron objects
-----------------------------------
As we demonstrated earlier, making changes to ``CatmaidNeuron/Lists`` will cause comparisons to fail. This also propagates into substractions and bitwise comparisons:
.. code:: ipython3
n1 = nl[0]
n1 in nl
.. parsed-literal::
True
.. code:: ipython3
n2 = n1.reroot(nl[0].tags['ends'][0], inplace=False)
n2 in nl
.. parsed-literal::
False
.. code:: ipython3
nl & n1
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57316 ML 2863105 |
2863104 |
5222 |
439 |
149 |
157 |
63 |
1109.8867 |
NA |
True |
.. code:: ipython3
nl & n2
.. parsed-literal::
WARNING : Skeleton IDs overlap but neuron not identical! Bitwise cancelled! Try using .skeleton_id instead. (pymaid)
See how the comparison fails because we've made changes? The same happens if you tried ``nl - n2``.
As mentioned in the warning, the workaround is to use the skeleton ID instead:
.. code:: ipython3
n2.skeleton_id in nl
.. parsed-literal::
True
.. code:: ipython3
nl & n2.skeleton_id
.. raw:: html
|
neuron_name |
skeleton_id |
n_nodes |
n_connectors |
n_branch_nodes |
n_end_nodes |
open_ends |
cable_length |
review_status |
soma |
| 0 |
PN glomerulus DA1 57316 ML 2863105 |
2863104 |
5222 |
439 |
149 |
157 |
63 |
1109.8867 |
NA |
True |