2016-03-24 2 views
5

Я работаю с массивами numpy разных типов данных. Я хотел бы знать, какого-либо определенного массива, элементами которого являются NaN. Обычно это то, за чем стоит np.isnan.np.isnan на массивах dtype "object"

Однако np.isnan не дружит с массивами типа данных object (или любой другой тип данных строки):

>>> str_arr = np.array(["A", "B", "C"]) 
>>> np.isnan(str_arr) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: Not implemented for this type 

>>> obj_arr = np.array([1, 2, "A"], dtype=object) 
>>> np.isnan(obj_arr) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe'' 

То, что я хотел бы выйти из этих двух вызовов просто np.array([False, False, False]). Я не могу просто поставить try и except TypeError вокруг моего звонка на np.isnan и предположить, что любой массив, который генерирует TypeError, не содержит NaN: в конце концов, мне бы хотелось, чтобы np.isnan(np.array([1, np.NaN, "A"])) вернулся np.array([False, True, False]).

Мое текущее решение состоит в том, чтобы создать новый массив типа np.float64, пропустить элементы исходного массива, try ing, чтобы поместить этот элемент в новый массив (и если он не сработает, оставьте его как ноль), а затем вызвав np.isnan на новый массив. Однако это, конечно, довольно медленно. (По крайней мере, для больших массивов объектов.)

def isnan(arr): 
    if isinstance(arr, np.ndarray) and (arr.dtype == object): 
     # Create a new array of dtype float64, fill it with the same values as the input array (where possible), and 
     # then call np.isnan on the new array. This way, np.isnan is only called once. (Much faster than calling it on 
     # every element in the input array.) 
     new_arr = np.zeros((len(arr),), dtype=np.float64) 
     for idx in xrange(len(arr)): 
      try: 
       new_arr[idx] = arr[idx] 
      except Exception: 
       pass 
     return np.isnan(new_arr) 
    else: 
     try: 
      return np.isnan(arr) 
     except TypeError: 
      return False 

Эта конкретная реализация также работает только для одномерных массивов, и я не могу думать о приличном образом, чтобы запустить в for петлю над произвольным числом Габаритные размеры.

Есть ли более эффективный способ выяснить, какие элементы в массиве являются NaN?

EDIT: Я запускаю Python 2.7.10.

Отметьте, что [x is np.nan for x in np.array([np.nan])] возвращает False: np.nan не всегда тот же объект в памяти, что и другой np.nan.

Я не хочу строку"nan" будет считаться эквивалентной np.nan: Я хочу isnan(np.array(["nan"], dtype=object)) вернуться np.array([False]).

Многомерность не является большой проблемой. (Ничего, что немного ravel -и- reshape ИНГ не исправит: стр.)

Любая функция, которая зависит от оператора is для проверки эквивалентности двух NaNs не всегда будет работать. (Если вы думаете, что должны, спросите себя, что делает оператор is!)

+0

Я бы попробовать «небезопасный» принуждение копию, хотя я не то, что строки будут endup как. Возможно, вам придется расколоться или сгладить. – hpaulj

+0

@hpaulj Не могли бы вы расширить свое предложение? Я не следую тому, что вы предлагаете. – acdr

ответ

3

Вы могли бы просто использовать список Комп, чтобы получить показатели любого Нэн, которые могут быть быстрее в этом случае:

obj_arr = np.array([1, 2, np.nan, "A"], dtype=object) 

inds = [i for i,n in enumerate(obj_arr) if str(n) == "nan"] 

Или, если вы хотите булево маску:

mask = [True if str(n) == "nan" else False for n in obj_arr] 

Использование is np.nan также, кажется, работает без необходимости бросить на ул:

In [29]: obj_arr = np.array([1, 2, np.nan, "A"], dtype=object) 

In [30]: [x is np.nan for x in obj_arr] 
Out[30]: [False, False, True, False] 

для плоских и multidime nsional массивы можно проверить форму:

def masks(a): 
    if len(a.shape) > 1: 
     return [[x is np.nan for x in sub] for sub in a] 
    return [x is np.nan for x in a] 

Если это np.nan может не возможно проверить тип, то нам np.isnan

def masks(a): 
    if len(a.shape) > 1: 
     return [[isinstance(x, float) and np.isnan(x) for x in sub] for sub in arr] 
    return [isinstance(x, float) and np.isnan(x) for x in arr] 

Интересно x is np.nan кажется, работает хорошо, когда тип данных объект:

In [76]: arr = np.array([np.nan,np.nan,"3"],dtype=object) 

In [77]: [x is np.nan for x in arr] 
Out[77]: [True, True, False] 

In [78]: arr = np.array([np.nan,np.nan,"3"]) 

In [79]: [x is np.nan for x in arr] 
Out[79]: [False, False, False] 

в зависимости от DTYPE разные вещи:

In [90]: arr = np.array([np.nan,np.nan,"3"]) 

In [91]: arr.dtype 
Out[91]: dtype('S32') 

In [92]: arr 
Out[92]: 
array(['nan', 'nan', '3'], 
     dtype='|S32') 

In [93]: [x == "nan" for x in arr] 
Out[93]: [True, True, False] 

In [94]: arr = np.array([np.nan,np.nan,"3"],dtype=object) 

In [95]: arr.dtype 
Out[95]: dtype('O') 

In [96]: arr 
Out[96]: array([nan, nan, '3'], dtype=object) 

In [97]: [x == "nan" for x in arr] 
Out[97]: [False, False, False] 

Очевидно, что Нэн получить принуждают к numpy.string_'s, когда у вас есть строки в массиве так x == "nan" работает в том случае, когда вы передаете объект типа с плавающей точкой, так что если вы всегда используете объект DTYPE то поведение должно быть последовательным.

+0

Вам не понадобится 'str (n) == 'nan''? – hpaulj

+0

@hpaulj, он есть? –

+0

Первое решение - это не то, что я ищу: мне не нужны индексы, а булевский массив. Второе решение всегда возвращает мне «False» (python 2.7.10.2): '[x is np.nan для x в np.array ([1, np.nan])]' оценивает '[False, False]' , Третье решение работает и примерно в два раза быстрее, чем моя функция на моем тестовом примере. Тем не менее, все же не работает для произвольно сформированных массивов. :( – acdr

0

Я бы использовал np.vectorize и пользовательскую функцию, которая проверяет для nan по-разному. Так,

def _isnan(x): 
    if isinstance(x, type(np.nan)): 
     return np.isnan(x) 
    else: 
     return False 

my_isnan = np.vectorize(_isnan) 

Тогда

X = np.array([[1, 2, np.nan, "A"], [np.nan, True, [], ""]], dtype=object) 
my_isnan(X) 

возвращает

array([[False, False, True, False], 
     [ True, False, False, False]], dtype=bool) 
+1

Пробуя это на массив из 1 миллиона элементов (повторяя '[" STRING_1 "," STRING_2 ", 1.0, np.NaN]'), это решение фактически составляет примерно в 2,5 раза больше времени для вычисления в качестве моей исходной функции. работа для произвольно сформированных массивов, так что это хорошо. – acdr

+0

Я обновил свой ответ с помощью другой функции, которая работает примерно в два раза быстрее. 'try except' действительно добавляет слишком много накладных расходов. Можете ли вы снова протестировать его против своей реализации? – Olaf

1

Определить пару тестовых массивов, малых и больше

In [21]: x=np.array([1,23.3, np.nan, 'str'],dtype=object) 
In [22]: xb=np.tile(x,300) 

Ваша функция:

In [23]: isnan(x) 
Out[23]: array([False, False, True, False], dtype=bool) 

Прямой вперед список понимание, возвращая массив

In [24]: np.array([i is np.nan for i in x]) 
Out[24]: array([False, False, True, False], dtype=bool) 

np.frompyfunc имеет аналогичную векторизации власть np.vectorize, но по какой-то причине используются недостаточно (и в моем опыте быстрее)

In [25]: def myisnan(x): 
     return x is np.nan 
In [26]: visnan=np.frompyfunc(myisnan,1,1) 

In [27]: visnan(x) 
Out[27]: array([False, False, True, False], dtype=object) 

Поскольку он возвращает объект dtype, мы можем указать его значения:

In [28]: visnan(x).astype(bool) 
Out[28]: array([False, False, True, False], dtype=bool) 

Он может обрабатывать массивы MultiDim красиво:

In [29]: visnan(x.reshape(2,2)).astype(bool) 
Out[29]: 
array([[False, False], 
     [ True, False]], dtype=bool) 

Теперь для некоторых таймингов:

In [30]: timeit isnan(xb) 
1000 loops, best of 3: 1.03 ms per loop 

In [31]: timeit np.array([i is np.nan for i in xb]) 
1000 loops, best of 3: 393 us per loop 

In [32]: timeit visnan(xb).astype(bool) 
1000 loops, best of 3: 382 us per loop 

Важным моментом при испытании i is np.nan - это относится только к скаляров. Если массив является объектом dtype, то итерация производит скаляры. Но для массива dtype float мы получаем значения numpy.float64. Для них np.isnan(i) является правильным тестом.

In [61]: [(i is np.nan) for i in np.array([np.nan,np.nan,1.3])] 
Out[61]: [False, False, False] 

In [62]: [np.isnan(i) for i in np.array([np.nan,np.nan,1.3])] 
Out[62]: [True, True, False] 

In [63]: [(i is np.nan) for i in np.array([np.nan,np.nan,1.3], dtype=object)] 
Out[63]: [True, True, False] 

In [64]: [np.isnan(i) for i in np.array([np.nan,np.nan,1.3], dtype=object)] 
... 
TypeError: Not implemented for this type 
+0

'x is np.nan' не всегда работает так, как вы его намереваетесь, потому что он проверяет личность, а не эквивалентность, и не все значения' np.nan' должны иметь одинаковый идентификатор. Например: '[y является np.nan для y в np.array ([np.nan, 1, 2, np.nan + 1], dtype = object)]' возвращает '[True, False, False, False] ', хотя первые * и * четвертые элементы - NaN. Любая функция, которая полагается на оператор 'is', не будет безупречной. – acdr

+0

Пример того, где он не работает: '>>> x = np.array ([np.float32 (np.nan), 'str'], dtype = object)' '>>> np.array ([i является np.nan для i в x]) ' ' array ([False, False], dtype = bool) ' – kungfujam

+0

Нам может понадобиться функция тестирования, которая связывает' np.cancast (x, float) 'before 'np.isnan (х)'. – hpaulj

-1

способ сделать это без преобразования в строку или выходя из среды Numpy (также очень важно ИМО) является использование определения равенства np.nan, где

In[1]: x=np.nan 
In[2]: x==x 
Out[2]: False 

Это верно только где x == np.nan. Таким образом, для массива Numpy, то поэлементно проверка

x!=x 

возвращается True для каждого элемента, где x==np.nan

+0

Кроме этого не работает. Numpy 1.9.2, Python 2.7.10: 'x = np.nan',' arr = np.array ([x], dtype = object) '. 'arr! = arr' возвращает' array ([False], dtype = bool) '. (Это связано с тем, что сравнение '==' сначала проверяет идентификатор ('is'). Это изменится в будущем, как отмечено numpy, создавая« FutureWarning », но я не буду в будущем. :) – acdr

+0

Право вас находятся! Спасибо, что поймал это. – jshrimp29

Смежные вопросы