首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么itertools.groupby可以在列表中将NaNs分组,而不能在numpy数组中分组

为什么itertools.groupby可以在列表中将NaNs分组,而不能在numpy数组中分组
EN

Stack Overflow用户
提问于 2017-01-18 15:37:26
回答 3查看 910关注 0票数 17

我很难调试一个问题,当在nan中使用浮点时,list中的浮点nannumpy.array中的nan被不同地处理

给定以下列表和数组:

代码语言:javascript
复制
from itertools import groupby
import numpy as np

lst = [np.nan, np.nan, np.nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, np.nan, 0.16]
arr = np.array(lst)

当我遍历列表时,连续的nan被分组:

代码语言:javascript
复制
>>> for key, group in groupby(lst):
...     if np.isnan(key):
...         print(key, list(group), type(key))
nan [nan, nan, nan] <class 'float'>
nan [nan] <class 'float'>

但是,如果我使用这个数组,它会将连续的nan放在不同的组中:

代码语言:javascript
复制
>>> for key, group in groupby(arr):
...     if np.isnan(key):
...         print(key, list(group), type(key))
nan [nan] <class 'numpy.float64'>
nan [nan] <class 'numpy.float64'>
nan [nan] <class 'numpy.float64'>
nan [nan] <class 'numpy.float64'>

即使我将数组转换回列表:

代码语言:javascript
复制
>>> for key, group in groupby(arr.tolist()):
...     if np.isnan(key):
...         print(key, list(group), type(key))
nan [nan] <class 'float'>
nan [nan] <class 'float'>
nan [nan] <class 'float'>
nan [nan] <class 'float'>

我在用:

代码语言:javascript
复制
numpy 1.11.3
python 3.5

我知道通常情况下是nan != nan,所以为什么这些操作会给出不同的结果?groupby怎么可能对nan进行分组呢?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-01-18 16:06:33

Python列表只是指向内存中对象的指针数组。特别地,lst保存指向对象np.nan的指针。

代码语言:javascript
复制
>>> [id(x) for x in lst]
[139832272211880, # nan
 139832272211880, # nan
 139832272211880, # nan
 139832133974296,
 139832270325408,
 139832133974296,
 139832133974464,
 139832133974320,
 139832133974296,
 139832133974440,
 139832272211880, # nan
 139832133974296]

(我的电脑上的np.nan是139832272211880。)

另一方面,NumPy数组只是内存的连续区域;它们是由位和字节组成的区域,它们被解释为值的序列(浮点数、ints等等)。由NumPy.

问题是,当您要求Python迭代包含浮动值的NumPy数组(在for、-loop或groupby级别上)时,Python需要将这些字节放入适当的Python对象中。它在内存中为数组中的每个值在迭代时创建一个全新的Python对象。

例如,您可以看到每个nan值的不同对象是在调用.tolist()时创建的:

代码语言:javascript
复制
>>> [id(x) for x in arr.tolist()]
[4355054616, # nan
 4355054640, # nan
 4355054664, # nan
 4355054688,
 4355054712,
 4355054736,
 4355054760,
 4355054784,
 4355054808,
 4355054832,
 4355054856, # nan
 4355054880]

itertools.groupby能够对np.nan列表进行分组,因为它在比较Python对象时首先检查身份。因为这些指向nan的指针都指向同一个np.nan对象,所以可以进行分组。

但是,NumPy数组上的迭代不允许这种初始身份检查成功,因此,正如您所说的,Python回到了检查等式和nan != nan

票数 9
EN

Stack Overflow用户

发布于 2017-01-18 17:58:00

K阿杰尔的答案是正确的,这是因为列表中的nans具有相同的id,而当它们在numpy数组中“迭代”时有不同的it。

这个答案是作为这些答案的补充。

代码语言:javascript
复制
>>> from itertools import groupby
>>> import numpy as np

>>> lst = [np.nan, np.nan, np.nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, np.nan, 0.16]
>>> arr = np.array(lst)

>>> for key, group in groupby(lst):
...     if np.isnan(key):
...         print(key, id(key), [id(item) for item in group])
nan 1274500321192 [1274500321192, 1274500321192, 1274500321192]
nan 1274500321192 [1274500321192]

>>> for key, group in groupby(arr):
...     if np.isnan(key):
...         print(key, id(key), [id(item) for item in group])
nan 1274537130480 [1274537130480]
nan 1274537130504 [1274537130504]
nan 1274537130480 [1274537130480]
nan 1274537130480 [1274537130480]  # same id as before but these are not consecutive

>>> for key, group in groupby(arr.tolist()):
...     if np.isnan(key):
...         print(key, id(key), [id(item) for item in group])
nan 1274537130336 [1274537130336]
nan 1274537130408 [1274537130408]
nan 1274500320904 [1274500320904]
nan 1274537130168 [1274537130168]

问题是Python在比较值时使用PyObject_RichCompare-operation,如果==由于没有实现而失败,只能测试对象标识。另一方面,itertools.groupby使用PyObject_RichCompareBool (请参阅来源:12),它首先测试对象标识,然后再对进行测试。

这可以用一个小的cython片段来验证:

代码语言:javascript
复制
%load_ext cython
%%cython

from cpython.object cimport PyObject_RichCompareBool, PyObject_RichCompare, Py_EQ

def compare(a, b):
    return PyObject_RichCompare(a, b, Py_EQ), PyObject_RichCompareBool(a, b, Py_EQ)

>>> compare(np.nan, np.nan)
(False, True)

PyObject_RichCompareBool的源代码如下所示:

代码语言:javascript
复制
/* Perform a rich comparison with object result.  This wraps do_richcompare()
   with a check for NULL arguments and a recursion check. */

/* Perform a rich comparison with integer result.  This wraps
   PyObject_RichCompare(), returning -1 for error, 0 for false, 1 for true. */
int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{
    PyObject *res;
    int ok;

    /* Quick result when objects are the same.
       Guarantees that identity implies equality. */
    /**********************That's the difference!****************/
    if (v == w) {
        if (op == Py_EQ)
            return 1;
        else if (op == Py_NE)
            return 0;
    }

    res = PyObject_RichCompare(v, w, op);
    if (res == NULL)
        return -1;
    if (PyBool_Check(res))
        ok = (res == Py_True);
    else
        ok = PyObject_IsTrue(res);
    Py_DECREF(res);
    return ok;
}

对象标识测试(if (v == w) )确实是在使用普通python比较PyObject_RichCompare(v, w, op);并在其文件中提及之前进行的。

注意: 如果o1和o2是同一个对象,PyObject_RichCompareBool()总是返回1表示Py_EQ,0返回Py_NE。

票数 7
EN

Stack Overflow用户

发布于 2017-01-18 16:08:17

我不确定这是否是原因,但我只是注意到了nan in lstarr

代码语言:javascript
复制
>>> lst[0] == lst[1], arr[0] == arr[1]
(False, False)
>>> lst[0] is lst[1], arr[0] is arr[1]
(True, False)

也就是说,虽然所有nan都是不相等的,但常规np.nan (类型为float)都是相同的实例,而arr中的nannumpy.float64类型的不同实例)。因此,我的猜测是,如果没有给出key函数,groupby将在进行更昂贵的等式检查之前测试标识。

这也与arr.tolist()中不分组的观察结果相一致,因为即使这些nan现在又是float,但它们不再是同一个实例。

代码语言:javascript
复制
>>> atl = arr.tolist()
>>> atl[0] is atl[1]
False
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41723419

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档