Почему модуле itertools.groupby группирует NaNs в списках, но не в массивах numpy
мне трудно отладить проблему, в которой float nan
на list
и nan
на numpy.array
обрабатываются по-разному, когда они используются в itertools.groupby
:
учитывая следующий список и массив:
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
s сгруппированы:
>>> 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
s в разных группах:
>>> 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'>
даже если я преобразую массив обратно в список:
>>> 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'>
я использую:
numpy 1.11.3
python 3.5
Я знаю, что обычно nan != nan
так почему же эти операции дают разные результаты? И как это возможно, что groupby
группы nan
s вообще?
3 ответов
списки Python - это просто массивы указателей на объекты в памяти. В частности lst
содержит указатели на объект np.nan
:
>>> [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-это просто смежные области памяти; это области битов и байтов, которые интерпретируются как последовательность значений (поплавки, Инты и т. д.) NumPy.
проблема в том, что когда вы просите Python перебирать NumPy массив с плавающими значениями (при a for
-петля или groupby
level), Python должен поместить эти байты в правильный объект Python. Он создает новый объект Python в памяти для каждого отдельного значения в массив, как это проходит.
например, вы можете видеть, что это разные объекты для каждого nan
значения создаются, когда .tolist()
называется:
>>> [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, потому что он проверяет личность сначала, когда он сравнивает объекты Python. Потому что эти указатели на nan
все точки На одном np.nan
объект, группировка возможна.
однако итерация по массиву NumPy не позволяет этой первоначальной проверке идентичности успешно, поэтому Python возвращается к проверке равенства и nan != nan
как вы говорите.
ответы tobias_k и ajcr правильно, потому что nan
s в списке имеют то же самое id
пока они имеют разные идентификаторы, когда они "перебираются" в массиве numpy.
этот ответ предназначен в качестве дополнения для этих ответов.
>>> 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
-операция при сравнении значений, которая проверяет только идентичность объекта, если ==
не удается, потому что это не реализовано. itertools.groupby
С другой стороны использует PyObject_RichCompareBool
(см. Источник: 1, 2), который проверяет идентичность объекта первый и до ==
проверен.
это можно проверить с помощью небольшого фрагмента цитона:
%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
звучит так:
/* 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);
is используется и упоминается в документация:
Примечание :
если o1 и o2-один и тот же объект,
PyObject_RichCompareBool()
всегда будет возвращать 1 для Py_EQ и 0 для Py_NE.
я не уверен, что это причина, но я только что заметил это о nan
на lst
и arr
:
>>> 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
) все то же самое экземпляр, в то время как nan
на arr
are разные экземпляры типа numpy.float64
). Так что я предполагаю, что если нет дали, groupby
будет тест на идентичность, прежде чем делать более дорогим проверка равенства.
это также согласуется с наблюдением, которое не группируется в arr.tolist()
либо, потому что хоть те nan
теперь float
опять же, они больше не являются одним и тем же экземпляром.
>>> atl = arr.tolist()
>>> atl[0] is atl[1]
False