¿Ya tenemos una función similar a np.add en matrices incómodas?

Estoy en una situación en la que necesito agregarlos, y el operador "+" funciona bien para una matriz simple pero no para una matriz anidada.

P.ej. >>> ak.to_list (c1)

[[], [], [], [], [0.944607075944902]]

>>> ak.to_list(c2)

[[0.9800207661211596], [], [], [], []]

>>> c1+c2

Rastreo (llamadas recientes más última): Archivo "", línea 1, en Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/numpy/lib/mixins.py", línea 21, en func return ufunc (yo, otro) Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/awkward1/highlevel.py", línea 1380, en array_ufunc return awkward1._connect._numpy.array_ufunc (ufunc, método, entradas, kwargs) Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/awkward1/_connect/_numpy.py", línea 107, en array_ufunc out = awkward1._util.broadcast_and_apply (entradas, getfunction, comportamiento) Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/awkward1/_util.py", línea 972, en broadcast_and_apply out = aplicar (broadcast_pack (entradas, isscalar), 0) Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/awkward1/_util.py", línea 745, en aplicar outcontent = aplicar (siguientes entradas, profundidad + 1) Archivo "/afs/cern.ch/work/k/khurana/EXOANALYSIS/CMSSW_11_0_2/src/bbDMNanoAOD/analyzer/dependencies/lib/python3.6/site-packages/awkward1/_util.py", línea 786, en aplicar nextinputs.append (x.broadcast_tooffsets64 (compensaciones) .content) ValueError: en ListOffsetArray64, no se puede transmitir la lista anidada

(https: / /github.com/scikit-hep/awkward-1.0/blob/0.3.1/src/cpu-kernels/operations.cpp#L778)

La única forma en que puedo agregarlos es usando los primeros y luego reemplazando Ninguno con 0.

>>> z1=ak.fill_none(ak.firsts(c1),0.)

>>> z2=ak.fill_none(ak.firsts(c2),0.)

>>> z1

<Array [0, 0, 0, 0, 0.945] type='5 * float64'>

>>> z2

<Array [0.98, 0, 0, 0, 0] type='5 * float64'>

>>> z1+z2

<Array [0.98, 0, 0, 0, 0.945] type='5 * float64'>

¿Se puede diseñar algo similar a np.add para ak incluso si tiene un alcance / funcionalidad limitados? Por alcance limitado me refiero a que si puede funcionar solo en la misma dimensión de una matriz k, al menos serviría para mi propósito actual.

Gracias.

1
Raman Khurana 20 oct. 2020 a las 02:29

1 respuesta

La mejor respuesta

La excepción que viste por

>>> ak.to_list(c1)
[[], [], [], [], [0.944607075944902]]

>>> ak.to_list(c2)
[[0.9800207661211596], [], [], [], []]

>>> c1+c2

Es correcto: no puede agregar estas dos matrices. No es porque Awkward carece de una función ak.add. Tal cosa sería idéntica a np.add:

>>> c1 + c2          # this actually calls np.add
<Array [[], [], [], [], [1.89]] type='5 * var * float64'>
>>> np.add(c1, c1)
<Array [[], [], [], [], [1.89]] type='5 * var * float64'>

No funciona porque las matrices tienen un número diferente de elementos en cada posición. Es como intentar agregar dos matrices NumPy con diferentes formas. (Puede agregar matrices NumPy con ciertas formas diferentes, al igual que puede agregar matrices Awkward con ciertas formas diferentes, si broadcast. Estos no.)

Si desea que una lista vacía se comporte como una lista con un cero, hizo lo correcto: ak.firsts y ak.singletons convierte entre dos formas de representar los datos faltantes:

  • como None frente a otro valor
  • como listas vacías frente al valor en una lista de longitud 1.

En algunos idiomas, un valor faltante o potencialmente faltante se trata como una lista de longitud-0 o longitud-1, como Tipo de opción de Scala. Por lo tanto,

>>> ak.firsts(c1)
<Array [None, None, None, None, 0.945] type='5 * ?float64'>

Supone que estaba comenzando desde vacío o singleton (parece ser cierto en sus ejemplos) y lo convierte en una matriz de tipo de opción con un nivel menos de profundidad. Luego, hacer un ak.fill_none significa que deseaba estos valores faltantes (que provienen de listas vacías) para actuar como ceros para sumar, y obtuviste lo que querías.

>>> ak.fill_none(ak.firsts(c1), 0) + ak.fill_none(ak.firsts(c2), 0)
<Array [0.98, 0, 0, 0, 0.945] type='5 * float64'>

Una cosa que no está clara a partir de sus datos es si siempre espera que las listas tengan como máximo un elemento: ak.firsts solo extraerá el primer elemento de cada lista. Si tuvieras

>>> c1 = ak.Array([[], [], [], [], [0.999, 0.123]])
>>> c2 = ak.Array([[0.98], [], [], [], []])

Entonces

>>> ak.fill_none(ak.firsts(c1), 0) + ak.fill_none(ak.firsts(c2), 0)
<Array [0.98, 0, 0, 0, 0.999] type='5 * float64'>

Puede que no sea lo que desea, ya que deja caer el 0.123. Es posible que desee ak.pad_none cada lista tener al menos un elemento, como este:

>>> ak.pad_none(c1, 1)
<Array [[None], [None], ... [0.999, 0.123]] type='5 * var * ?float64'>
>>> ak.fill_none(ak.pad_none(c1, 1), 0)
<Array [[0], [0], [0], [0], [0.999, 0.123]] type='5 * var * float64'>

Esto mantiene la estructura, distinguiendo entre longitudes de lista para todas las longitudes excepto 0 y 1, porque las listas vacías se han convertido en [0]. No puede usar esto para agregar a menos que estas listas más largas coincidan con la longitud (de regreso a su problema original), pero también puede arreglar eso.

>>> ak.fill_none(ak.pad_none(c1, 2), 0) + ak.fill_none(ak.pad_none(c2, 2), 0)
<Array [[0.98, 0], [0, ... 0], [0.999, 0.123]] type='5 * var * float64'>

Todo depende de las estructuras que tengas y de las que quieras. No sería una buena idea crear una nueva función que haga una de las dos cosas anteriores, especialmente si tiene un nombre que se acerca peligrosamente al de una función NumPy, como np.add, porque funciona de una manera diferente eso tendría que ser explicado para que cualquiera pueda usarlo de manera segura. Si desea hacer algo especializado, es más seguro que lo construya a partir de primitivas más simples (incluso si lo resume como una función de conveniencia en su propio trabajo), porque entonces sabrá qué reglas sigue.

1
Jim Pivarski 20 oct. 2020 a las 00:17