2017-01-18 2 views
15

У меня трудное время, чтобы отладить проблему, в которой поплавок nan в list и nan в numpy.array обрабатываются по-разному, когда они используются в itertools.groupby:Почему itertools.groupby Сгруппируйте в списках пренебрежимо малых, но не в 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) 

Когда я итерацию по списку сопредельные 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 с в разных группах:

>>> 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?

+0

Хм, интересно, что вызвало этот вопрос. :) – Tagc

+0

В чем именно вопрос: почему 'groupby' показал это непоследовательное поведение или как его обойти? И в последнем случае вы хотите, чтобы «нан» сгруппированы или нет? –

+0

Просто, если кого-то интересует, вопрос был задан, когда я попытался выяснить, почему [ответ] (http://stackoverflow.com/a/41722363/5393381) работал, когда применяется к списку, но не при применении к 'np.array'. – MSeifert

ответ

8

Списки 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 массивы только смежные области памяти; они представляют собой области битов и байтов, которые интерпретируются как последовательность значений (float, ints и т. д.) с помощью NumPy.

Проблема заключается в том, что при запросе Python на итерацию по массиву NumPy, содержащему плавающие значения (на уровне for -loop или groupby), 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, как вы говорите.

5

Я не уверен, является ли причина, но я только что заметил, это о 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) 

Ie, в то время как все nan являются неровными, регулярным np.nan (типа float) все являются такими же экземпляром, в то время как nan в arr являются разные экземпляры типа numpy.float64). Поэтому я предполагаю, что если функция key указана, groupby проверит идентификацию перед тем, как выполнить более дорогостоящую проверку равенства.

Это также согласуется с наблюдением, что не является не группа в arr.tolist() либо, потому что даже если те nan теперь float снова, они больше не тот же экземпляр.

>>> atl = arr.tolist() 
>>> atl[0] is atl[1] 
False 
+1

Итак, 'math.nan' - это одноэлемент, а' np.nan' - одноэлементный, но доступ к 'np.array' создает временный float, который * не является * singleton? Взорвался разум. –

+1

@MarkRansom не только 'np.array .__ getitem__' создает другой объект для nan, когда вы хотите получить к нему доступ. Но каждый раз, когда вы вручную создаете 'nan' (например,' float ('nan') '), создается новый _not-singleton_ nan. Забавный факт: 'np.nan не является math.nan'! – MSeifert

6

Ответы 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 -operation при сравнении значений, что только тесты для идентичности объекта, если == терпит неудачу, потому что это не реализовано. itertools.groupby, с другой стороны, использует PyObject_RichCompareBool (см. Источник: 1, 2), который проверяет идентификацию объекта сперва и до == проверено.

Это можно проверить с помощью небольшой Cython фрагмент кода:

%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); используется и указывается в its documentation:

Примечание:

Если o1 и o2 являются одним и тем же объектом, PyObject_RichCompareBool() всегда будет возвращать 1 для Py_EQ и 0 для Py_NE.