我想使用NumPy C-API编写一个NumPy Gufunc Python扩展,它接受任意维度的两个矩阵,取一个维度的平均值,然后从另一个维度减go 一个结果.最简单的例子是两个带有签名‘(I),(J)->()’的一维数组,它返回一个标量.

任何附加维度都可以被假定为核心维度,因此是相同的.例如,二维数组签名可能是‘(n,i),(n,j)->(N)’,其中核心维度是Axis=0,并且将简单地在该轴上循环该函数.以下是我目前掌握的情况:

#include <Python.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>
#include <math.h>

static void mean_diff(char **args,
                      const npy_intp *dimensions,
                      const npy_intp* steps,
                      void* extra) {
    npy_intp i;
    npy_intp n1 = 0, n2 = 0;
    double s1 = 0.0, s2 = 0.0;
    char *in1 = args[0], *in2 = args[1], *out = args[2];
    npy_intp len1 = dimensions[0], len2 = dimensions[1];

    for (i = 0; i < len1; i++) {
        double val = *((double *)in1);
        if (!isnan(val)) {
            s1 += val;
            n1++;
        }
        in1 += steps[0];
    }

    for (i = 0; i < len2; i++) {
        double val = *((double *)in2);
        if (!isnan(val)) {
            s2 += val;
            n2++;
        }
        in2 += steps[1];
    }

    double mean1 = (n1 > 0) ? s1 / n1 : 0.0;
    double mean2 = (n2 > 0) ? s2 / n2 : 0.0;

    *((double *)out) = mean1 - mean2;
}

我的问题是,即使是一组简单的2个一维数组,也只考虑每个数组的第一个元素.例如:

>>> mean_diff([1.,2.,3.,4.], [2.,7.,29.,3.])
-1.0
>>> np.mean([1.,2.,3.,4.]) - np.mean([2.,7.,29.,3.])
-7.75

我认为这与抓取了错误的维度或什么的有关,但我不知道是什么原因.

推荐答案

首先要知道的是,您的函数应该在每次调用中多次计算结果.计算次数存储在dimensions[0]中.您指定了gufunc签名(i),(j)->().当调用该函数时,dimensions[1]dimensions[2]将保持ij的GUFUC签名(即,核心操作的1-D输入的长度).

例如,在此代码片段中,

x = np.array([[0, 1, 2, 3, 4], [0, 0, 0, 1, 1.5]])  # shape (2, 5)
y = np.array([[-1, -3, 7], [2, 2, 2]])              # shape (2, 3)
result = mean_diff(x, y)                            # shape (2,)

您的函数将被称为once,将dimensions[0]设置为2.dimensions[1]dimensions[2]将分别为5和3.您的外部循环将计算([0, 1, 2, 3, 4], [-1, -3, 7])对的结果,然后计算([0, 0, 0, 1, 1.5], [2, 2, 2])对的结果.

args[0]args[1]将指向输入数组的第一个元素,args[2]将指向输出数组的第一个元素.您的代码执行in1 = args[0]in2 = args[1]out = args[2],因此它必须计算in1in2指向的数据的结果,并将结果存储在*out中.然后,它必须递增in1in2out以指向下一组数据并再次进行计算.因此,您将实现一个外部循环,该循环 for each 输入对执行计算,并递增指针以移动到下一组输入和输出.增量in1in2out的量分别存储在steps[0]steps[1]steps[2]中.(例如,steps[0]是从x的第一行到第二行在内存中跳转的字节数.)

然后,在这个外部循环中,对当前从xy(由in1in2指向)的数组对进行计算.通常,您不能假设数组的元素是连续的,因此在计算平均值时,您必须按适当的步长递增指向数据的指针;在本例中,这些步长是steps[3]steps[4].

这里有一种方法可以实现这一点.我已经注释了我从dimensionssteps中提取的变量,以帮助澄清我上面的简洁描述.

static void mean_diff(
    char **args,
    const npy_intp *dimensions,
    const npy_intp *steps,
    void *extra)
{
    char *in1 = args[0];
    char *in2 = args[1];
    char *out = args[2];

    npy_intp nloops = dimensions[0];  // Number of outer loops
    npy_intp len1 = dimensions[1];    // Core dimension i
    npy_intp len2 = dimensions[2];    // Core dimension j

    npy_intp step1 = steps[0];        // Outer loop step size for the first input
    npy_intp step2 = steps[1];        // Outer loop step size for the second input
    npy_intp step_out = steps[2];     // Outer loop step size for the output
    npy_intp innerstep1 = steps[3];   // Step size of elements within the first input
    npy_intp innerstep2 = steps[4];   // Step size of elements within the second input

    for (npy_intp i = 0; i < nloops;
         i++, in1 += step1, in2 += step2, out += step_out) {

        // The core calculation.  in1 and in2 point to the 1-d input arrays,
        // and out points to the output array.

        double s1 = 0.0;
        double s2 = 0.0;
        npy_intp n1 = 0;
        npy_intp n2 = 0;

        for (npy_intp j = 0; j < len1; ++j) {
            double val = *(double *)(in1 + j*innerstep1);
            if (!isnan(val)) {
                s1 += val;
                n1++;
            }
        }

        for (npy_intp j = 0; j < len2; ++j) {
            double val = *(double *)(in2 + j*innerstep2);
            if (!isnan(val)) {
                s2 += val;
                n2++;
            }
        }

        double mean1 = (n1 > 0) ? s1 / n1 : 0.0;
        double mean2 = (n2 > 0) ? s2 / n2 : 0.0;

        *((double *)out) = mean1 - mean2;
    }
}

顺便说一句,如果像这样实现的gufunc实际上比用mean方法计算平均值并减go 结果要少slower,也不要感到惊讶.NumPy正在不断改进其性能,对于最近的SIMD实现,uuncf和guuncf的朴素实现可能比几个优化的NumPy方法调用的组合要慢.

Python相关问答推荐

如何在具有重复数据的pandas中对groupby进行总和,同时保留其他列

pandas滚动和窗口中有效观察的最大数量

为什么这个带有List输入的简单numba函数这么慢

为什么默认情况下所有Python类都是可调用的?

如何使用数组的最小条目拆分数组

我们可以为Flask模型中的id字段主键设置默认uuid吗

Godot:需要碰撞的对象的AdditionerBody2D或Area2D以及queue_free?

如果值发生变化,则列上的极性累积和

如何在Polars中从列表中的所有 struct 中 Select 字段?

如何使regex代码只适用于空的目标单元格

为什么常规操作不以其就地对应操作为基础?

如何将数据帧中的timedelta转换为datetime

比Pandas 更好的 Select

使用__json__的 pyramid 在客户端返回意外格式

一个telegram 机器人应该发送一个测验如何做?""

多索引数据帧到标准索引DF

我如何处理超类和子类的情况

如何在Quarto中的标题页之前创建序言页

高效地计算数字数组中三行上三个点之间的Angular

Pandas 删除只有一种类型的值的行,重复或不重复