1

尝试使用不同长度的锯齿状数组绘制二维直方图或图形时遇到问题。

这是一个简单的例子。假设有 7 个 gen 级 pT 及其 Et 事件。

pT = [ [46.8], [31.7], [21], [29.9], [13.9], [41.2], [15.7] ]
Et = [ [41.4], [25.5, 20], [19.6], [27.4], [12, 3.47], [37.8], [10] ]

这里,一些事件(2nd、5th)有两个 y 值对应一个 x 值。我想制作图表或二维直方图,将 x = pt 和 y = et 放在一起,并将两个点放在一起。即 (31.7, 25.5) 和 (31.7, 20)

如何使这些值对齐以进行绘图?

4

1 回答 1

1

您要做的是“广播”这两个数组:

尴尬的广播是 NumPy 广播的推广,包括可变长度列表。

当您执行数学计算时,广播通常会自动发生:

>>> import awkward1 as ak
>>> ak.Array([[1, 2, 3], [], [4, 5]]) + ak.Array([100, 200, 300])
<Array [[101, 102, 103], [], [304, 305]] type='3 * var * int64'>

但您也可以手动完成:

>>> ak.broadcast_arrays(ak.Array([[1, 2, 3], [], [4, 5]]),
...                     ak.Array([100, 200, 300]))
[<Array [[1, 2, 3], [], [4, 5]] type='3 * var * int64'>,
 <Array [[100, 100, 100], [], [300, 300]] type='3 * var * int64'>]

当两个数组具有不同的深度(NumPy 术语中的不同“维度”)时,来自一个数组的标量被复制以与另一个数组中的所有列表元素对齐。

您有两个相同深度的列表:

>>> pT = ak.Array([ [46.8], [31.7], [21], [29.9], [13.9], [41.2], [15.7] ])
>>> Et = ak.Array([ [41.4], [25.5, 20], [19.6], [27.4], [12, 3.47], [37.8], [10] ])

pT要手动广播它们,您可以通过从每个列表中获取第一个元素来减少深度。

>>> pT[:, 0]
<Array [46.8, 31.7, 21, ... 13.9, 41.2, 15.7] type='7 * float64'>

然后,您可以将 的每个标量广播pT到 的每个列表中Et

>> ak.broadcast_arrays(pT[:, 0], Et)
[<Array [[46.8], [31.7, 31.7, ... 41.2], [15.7]] type='7 * var * float64'>,
 <Array [[41.4], [25.5, 20], ... [37.8], [10]] type='7 * var * float64'>]

如果我通过将它们转换为 Python 列表来完整地打印它们,这将更加清楚:

>>> pT_broadcasted, Et = ak.broadcast_arrays(pT[:, 0], Et)
>>> pT_broadcasted.tolist()
[[46.8], [31.7, 31.7], [21.0], [29.9], [13.9, 13.9], [41.2], [15.7]]
>>> Et.tolist()
[[41.4], [25.5, 20.0], [19.6], [27.4], [12.0, 3.47], [37.8], [10.0]]

现在您看到31.7已被复制以与 中的每个值对齐[25.5, 20.0]

在 NumPy 中,您经常会看到广播长度为 1 的维度的示例,而不是创建维度,如下所示:

>>> import numpy as np
>>> np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + np.array([[100], [200], [300]])
array([[101, 102, 103],
       [204, 205, 206],
       [307, 308, 309]])

Awkward Array 遵循此规则,但前提是维度的长度“恰好为 1”,而不是“碰巧每个长度为 1 的一堆可变长度列表”。我写的方式pT,它有后者:

>>> ak.type(pT)     # 7 lists with variable length
7 * var * float64
>>> ak.num(pT)      # they happen to each have length 1... this time...
<Array [1, 1, 1, 1, 1, 1, 1] type='7 * int64'>

由于这些列表是原则上的变量,它们不会像长度为 1 的 NumPy 数组那样广播。

>>> ak.broadcast_arrays(pT, Et)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jpivarski/irishep/awkward-1.0/awkward1/operations/structure.py", line 699, in broadcast_arrays
    out = awkward1._util.broadcast_and_apply(inputs, getfunction, behavior)
  File "/home/jpivarski/irishep/awkward-1.0/awkward1/_util.py", line 972, in broadcast_and_apply
    out = apply(broadcast_pack(inputs, isscalar), 0)
  File "/home/jpivarski/irishep/awkward-1.0/awkward1/_util.py", line 745, in apply
    outcontent = apply(nextinputs, depth + 1)
  File "/home/jpivarski/irishep/awkward-1.0/awkward1/_util.py", line 786, in apply
    nextinputs.append(x.broadcast_tooffsets64(offsets).content)
ValueError: in ListOffsetArray64, cannot broadcast nested list

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

如果您将数组显式转换为 NumPy,它将具有常规类型。(自我注意:如果有一种方法可以在不将整个数组转换为 NumPy 的情况下将可变长度维度变为正则或反之亦然。)

>>> ak.type(pT)
7 * var * float64
>>> ak.type(ak.to_numpy(pT))
7 * 1 * float64

因此,获得相同广播的另一种方法是转换pT为 NumPy,而不是使用pT[:, 0].

>>> ak.broadcast_arrays(ak.to_numpy(pT), Et)
[<Array [[46.8], [31.7, 31.7, ... 41.2], [15.7]] type='7 * var * float64'>,
 <Array [[41.4], [25.5, 20], ... [37.8], [10]] type='7 * var * float64'>]

无论哪种方式,都会做出一个pT由长度为 1 的列表组成的假设。该pT[:, 0]表达式假设这一点,因为它需要0在每个列表中都有索引(因此长度至少为 1)并且它忽略了可能存在的任何其他内容。如果数组不是正则的,则表达式将引发异常,这是一种可以用 NumPy 表示的形状ak.to_numpy(pT)pT

现在您已经拥有pT_broadcastedEt与相同的结构对齐,您必须将它们都展平以将它们传递给绘图例程(它需要非锯齿状数据)。

>>> ak.flatten(pT_broadcasted), ak.flatten(Et)
(<Array [46.8, 31.7, 31.7, ... 13.9, 41.2, 15.7] type='9 * float64'>,
 <Array [41.4, 25.5, 20, ... 3.47, 37.8, 10] type='9 * float64'>)

绘图例程可能会尝试np.asarray其中的每一个,这与 相同ak.to_numpy,这将起作用,因为这些展平的数组是规则的。如果你有双重锯齿状的数据或更复杂的东西,你必须更加扁平化。

于 2020-09-24T17:30:35.093 回答