我该如何选择性地对数组的多个轴进行求和?

问题描述 投票:7回答:1

J中用于选择性地求和数组的多个轴的首选方法是什么?

例如,假设a是以下rank 3数组:

   ]a =: i. 2 3 4
 0  1  2  3
 4  5  6  7
 8  9 10 11

12 13 14 15
16 17 18 19
20 21 22 23

我的目标是定义一个二元“sumAxes”来总结我选择的多个轴:

   0 1 sumAxes a      NB. 0+4+8+12+16+20 ...
60 66 72 78

   0 2 sumAxes a      NB. 0+1+2+3+12+13+14+15 ...
60 92 124

   1 2 sumAxes a      NB. 0+1+2+3+4+5+6+7+8+9+10+11 ...
66 210

我目前试图实现这个动词的方式是使用dyad |:首先置换a的轴,然后使用,"n(其中n是我想要求和的数字轴)对所需等级的项目进行拉伸。总结结果项目:

   sumAxes =: dyad : '(+/ @ ,"(#x)) x |: y'

这看起来像我想要的那样工作,但作为J的初学者,我不确定我是否忽略了等级或特定动词的某些方面,这将使得更清晰的定义。更一般地说,我想知道在这种语言中,置换轴,ravelling和求和是否是惯用的或有效的。

对于上下文,我之前使用Python的NumPy库的大部分数组编程经验。

NumPy没有J的排名概念,而是希望用户明确标记数组的轴以减少:

>>> import numpy
>>> a = numpy.arange(2*3*4).reshape(2, 3, 4) # a =: i. 2 3 4
>>> a.sum(axis=(0, 2))                       # sum over specified axes
array([ 60,  92, 124])

作为一个脚注,当我指定单个轴时(由于rank不能与“axis”互换),我目前对sumAxes的实现具有与NumPy相比“不正确”工作的缺点。

arrays j
1个回答
10
投票

动机

J具有处理任意排序数组的难以置信的设施。但是这种语言的一个方面同时几乎普遍有用和合理,但也与这种维度不可知的本质有些对立。

主轴(实际上,通常是引导轴)是隐式特权的。这是作为基础的概念,例如, #是项目的数量(即第一轴的尺寸),+/的低调优雅和普遍性,没有进一步修改,以及该语言的许多其他美丽部分。

但这也是解决这个问题时遇到的障碍的原因。

标准方法

所以解决问题的一般方法就像你拥有它一样:转置或重新排列数据,使你感兴趣的轴成为引导轴。你的方法是经典和无懈可击的。你可以在良心上使用它。

替代方法

但是,就像你一样,它让我有点尴尬,我们被迫在类似情况下跳过这样的箍。我们有点反对语言的一个线索是连接"(#x)的动态论证;通常连词的参数是固定的,并且在运行时计算它们通常会迫使我们使用显式代码(如在您的示例中)或dramatically more complicated code。当语言难以做到的时候,这通常是你正在削减粮食的一个标志。

另一个是拉威尔(,)。这不只是我们想要转置一些轴;我们希望将焦点放在一个特定的轴上,然后将所有跟随它的元素运行到一个平面向量中。虽然我实际上认为这更多地反映了我们如何构建问题所带来的限制,而不是符号中的一个。更多关于这篇文章的最后一节。

有了这个,我们可能觉得我们希望直接解决非引导轴。而且,在这里和那里,J提供的原语允许我们完全这样做,这可能暗示语言的设计者也觉得需要在引导轴的首要地位中包含某些例外。

Introductory examples

例如,二元|.(旋转)具有排名1 _,即它在左侧采用向量。

对于那些已经使用它多年的人来说,这有时令人惊讶,从来没有超过左边的标量。与未绑定的右等级一起,是J的前导轴偏差的另一个微妙结果:我们将正确的参数视为项的向量,将左参数视为该向量的简单标量旋转值。

从而:

   3 |. 1 2 3 4 5 6
4 5 6 1 2 3

   1 |. 1 2 , 3 4 ,: 5 6
3 4
5 6
1 2

但是在后一种情况下,如果我们不想将表视为行的向量,而是作为列的向量,该怎么办?

当然,经典的方法是使用rank来明确表示我们感兴趣的轴(因为隐藏它总是选择引导轴):

   1 |."1 ] 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

现在,这在J代码中完全是惯用的,标准的,无处不在的:J鼓励我们按等级来思考。没有人会眨眼睛阅读这段代码。

但是,正如一开始所描述的那样,在另一种意义上,它可以感觉像是一个警察或手动调整。特别是当我们想要在运行时动态选择排名时。在符号上,我们现在不再是整个数组,而是解决每一行。

而这就是|.左派的位置:它是can address non-leading axes directly的少数基元之一。

   0 1 |. 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

看马,没有等级!当然,我们现在必须独立地为每个轴指定一个旋转值,但这不仅好,它很有用,因为现在左边的参数更像是可以从输入计算的东西,真正的J精神。

Summing non-leading axes directly

所以,既然我们知道J允许我们在某些情况下处理非引导轴,我们只需要to survey those cases并找出一个似乎适合我们目的的方法。

我发现最常用于非前导轴工作的原语是带有盒装左手参数的;.。所以我的直觉就是先达到目标。

让我们从您的示例开始,稍加修改以查看我们的总结。

    ]a =: i. 2 3 4
    sumAxes =: dyad : '(< @ ,"(#x)) x |: y'

     0 1 sumAxes a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+ 
     0 2 sumAxes a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
    1 2 sumAxes a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+

the definition of for dyads derived from ;.1和朋友的相关部分是:

二元情形1_12_2中的音符由布尔向量x中的1s决定;空向量x和非零#y表示整个y。如果x是原子01它被视为(#y)#x。通常,布尔向量>j{x指定轴j如何被切割,原子被视为(j{$y)#>j{x

这意味着:如果我们只是尝试沿着它的尺寸切割数组而没有内部分割,我们可以简单地使用带有仅由1s和a:s组成的左参数的二元切割。向量中的1s数(即总和)决定了结果数组的等级。

因此,重现上面的例子:

     ('';'';1) <@:,;.1 a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+
     ('';1;'') <@:,;.1 a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
     (1;'';'') <@:,;.1 a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+

瞧瞧。另外,请注意左手参数中的模式?两个ace正好是你对sumAxe的原始调用的指数。看看我的意思是,在J精神中为每个维度提供一个嗅觉就像一件好事的价值?

因此,使用这种方法为sumAxe提供相同接口的模拟:

   sax =: dyad : 'y +/@:,;.1~ (1;a:#~r-1) |.~ - {. x -.~ i. r=.#$y'     NB. Explicit
   sax =: ]  +/@:,;.1~  ( (] (-@{.@] |. 1 ; a: #~ <:@[) (-.~ i.) ) #@$) NB. Tacit

结果为简洁而省略,但它们与你的sumAxe相同。

Final considerations

还有一件事我想指出。从Python中调用的sumAxe调用的接口命名了你想要“一起运行”的两个轴。这绝对是看待它的一种方式。

另一种看待它的方式,它借鉴了我在这里提到的J哲学,就是命名你想要求和的轴。事实上,这是我们的实际焦点,这一事实证明了我们对每个“切片”的理解,因为我们不关心它的形状,只关心它的价值。

在谈论你感兴趣的事物的观点上的这种变化,有一个优点,它总是一件事,而这个奇点允许我们的代码中的某些简化(再次,特别是在J,我们通常谈论[新的] ,即后转置]引导轴)¹。

让我们再看看我们对;.的一对一矢量参数,来说明我的意思:

     ('';'';1) <@:,;.1 a
     ('';1;'') <@:,;.1 a
     (1;'';'') <@:,;.1 a

现在将三个带括号的参数视为三行的单个矩阵。你有什么突出的?对我来说,它是沿着反对角线的那些。它们数量较少,并且具有价值;相比之下,aces形成矩阵的“背景”(零)。这些是真实的内容。

这与我们的sumAxe接口现在的情况形成对比:它要求我们指定aces(零)。相反,我们如何指定1,即实际感兴趣的轴?

如果我们这样做,我们可以重写我们的功能:

  xas  =: dyad : 'y +/@:,;.1~ (-x) |. 1 ; a: #~ _1 + #$y'  NB. Explicit
  xas  =: ]  +/@:,;.1~  -@[ |. 1 ; a: #~ <:@#@$@]          NB. Tacit

而不是调用0 1 sax a,你打电话给2 xas a,而不是0 2 sax a,你打电话给1 xas a等。

这两个动词的相对简单性表明J同意这种焦点反转。


¹在这段代码中我假设你总是想要折叠除1之外的所有轴。这个假设是用我用来生成一对一和矢量的方法编码的,使用|.

但是,当仅指定单个轴时,您的脚注sumAxes具有与NumPy相比“不正确”工作的缺点,建议有时您只想折叠一个轴。

这是完全可能的,;.方法可以采取任意(原位)切片;我们只需要改变我们指示它的方法(生成1s-and-aces向量)。如果您提供了一些您想要的概括示例,我将在此处更新帖子。可能只是使用(<1) x} a: #~ #$y((1;'') {~ (e.~ i.@#@$))而不是(-x) |. 1 ; a:#~<:#$y

© www.soinside.com 2019 - 2024. All rights reserved.