Разумный способ иметь разные версии None?

#python #python-3.x #numpy #nonetype

#python #python-3.x #numpy #nonetype

Вопрос:

Работа в Python3.

Допустим, у вас миллион жуков, и ваша задача — каталогизировать размер их пятен. Итак, вы создадите таблицу, где каждая строка представляет собой beetle, а число в строке представляет размер пятен;

  [[.3, 1.2, 0.5],
  [.6, .7],
  [1.4, .9, .5, .7],
  [.2, .3, .1, .7, .1]]
  

Кроме того, вы решаете сохранить это в массиве numpy, для которого вы добавляете в списки значение None (numpy преобразует это в np.nan).

  [[.3, 1.2, 0.5, None, None],
  [.6, .7, None, None, None],
  [1.4, .9, .5, .7, None],
  [.2, .3, .1, .7, .1]]
  

Но есть проблема, значения, представленные как None, могут быть None по одной из 3 причин;

  1. В beetle не так много точек; такого количества не существует.

  2. Жук не будет стоять на месте, и вы не сможете измерить пятно.

  3. Вы еще не удосужились измерить этого жука, поэтому значение не назначено.

На самом деле моя проблема не связана с beetles, но принципы те же. Мне нужны 3 разных значения None, чтобы я мог различать причины отсутствия значений. Мое текущее решение заключается в использовании значения настолько большого, что оно физически маловероятно, но это не очень безопасное решение.

Предположим, вы не можете использовать отрицательные числа — в действительности величина, которую я измеряю, может быть отрицательной.

Данные большие, и скорость чтения важна.

Редактировать; комментарии справедливо указывают на то, что говорить о скорости, не указывая, какие операции, немного бессмысленно. Анализ основных компонентов, вероятно, будет использоваться для декорриляции переменных, вычисления квадрата евклидова расстояния для алгоритма кластеризации (но данных в этой переменной мало), возможно, некоторой интерполяции. В конечном итоге будет создана рекурсивная нейронная сеть, но она будет поступать из библиотеки, поэтому мне просто нужно будет ввести данные в форму ввода. Так что, возможно, нет ничего хуже линейной алгебры, все это должно поместиться в оперативной памяти, если я буду осторожен, я думаю.

Какая хорошая стратегия?

Комментарии:

1. Если вам нужны разные значения, не используйте None . Используйте какой-нибудь другой тип. Это не то, для чего None предназначено.

2. Если вы используете массив numpy, почему использование np.nan , np.inf и -np.inf не является решением?

3. Может быть, добавить столбец с количеством мест? Это показывает, сколько других столбцов имеют допустимое значение, и для других случаев могут быть специальные значения, такие как -1 и -2.

4. Будут ли замаскированные массивы иметь смысл для вашей проблемы? Это один из способов представления недопустимых / отсутствующих данных. Хотя я не знаю, насколько это повлияет на производительность.

5. Создайте Enum , значения которого являются разными вариантами None .

Ответ №1:

Самым простым способом было бы использовать строки: ‘not counted’, ‘unknown’ и ‘N / A’. Однако, если вы хотите быстро обрабатывать в numpy, массивы со смешанными числами / объектами — не ваш друг.

Моим предложением было бы добавить несколько массивов той же формы, что и ваши данные, состоящих из 0 и 1. Таким образом, массив missing = 1, где отсутствует spot, еще 0, и так далее, то же самое с array not_measured и т.д..

Тогда вы можете использовать NAN везде, а позже замаскировать свои данные, скажем, np.where(missing == 1) , чтобы легко найти конкретные NAN, которые вам нужны.

Ответ №2:

Если вам просто нужен объект, который не имеет никакого известного значения, а также не является им None , просто создайте новый объект:

 NOT_APPLICABLE = object()
NOT_MEASURED = object()
UNKNOWN = object()
  

Теперь вы можете просто использовать эти значения точно так же, как вы бы использовали None :

 [1.4, .9, .5, .7, UNKNOWN]

...

if value is UNKNOWN:
    # do something
  

и т.д.

Если вам нужно значение, которое может быть представлено в виде float (например, в numpy массиве), вы можете создать значение NaN с «дополнительными» данными, закодированными в мантиссе. Однако это может быть не безопасно, поскольку нет гарантии, что эти биты сохраняются с помощью различных операций со значениями.

Комментарии:

1. Но, я думаю, это собьет с толку Numpy. Он может преобразовать None в NaN и сохранить его в виде массива с плавающими значениями, но если там есть случайные объекты Python, это не сработает.

2. Я, честно говоря, не знаю, что numpy делает в этом случае. Я полагаю, что он может хранить произвольные объекты в массиве, он просто переключается на другой тип массива. Но я не знаю, как еще вы могли бы делать подобные вещи.

3. OP сказал, что это массив numpy (хотя, вероятно, следовало бы пометить его). Я думаю, что здесь также важна производительность, поэтому он / она просто не использует строки (которые совпадают с объектами с точки зрения скорости numpy)

4. @DanielPryden да, извините, я немного похоронил это. Причиной заполнения с помощью None является преобразование в массив numpy. Это сделано для повышения скорости различных последующих операций. Это кажется хорошим решением, хотя, возможно, мне придется выяснить, как это сделать способом, совместимым с numpy.

5. Для этого потребуется массив dtype=object, что лишит преимущества numpy в скорости.

Ответ №3:

Вот решение (отказ от ответственности: ВЗЛОМ!), которое позволяет избежать проблем со скоростью, таких как object dtype или отдельные маски:

Похоже, что вокруг fp-представления nan существует довольно много «мертвого пространства»:

 >>> nan_as_int = np.array(np.nan).view(int)[()]
>>> nan_as_int
9221120237041090560

>>> custom_nan = np.arange(nan_as_int, nan_as_int 10).view(float)
>>> custom_nan
array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan])
  

Мы создали десять различных nan файлов. Пожалуйста, обратите внимание, что это отличается от создания нескольких экземпляров с помощью float("nan") . Все такие экземпляры будут сопоставляться одному и тому же значению в numpy и, следовательно, будут неразборчивы после помещения в не объектный массив.

Даже при том, что наши ten nan имеют разные представления, на уровне float их трудно отличить друг от друга (потому что по определению nan != nan даже для unique nan ). Итак, нам нужен небольшой помощник:

 >>> def which_nan(a):
...     some_nan = np.isnan(a)
...     return np.where(some_nan, np.subtract(a.view(int), nan_as_int, where=some_nan), -1)
  

Пример:

 >>> exmpl = np.array([0.1, 1.2, custom_nan[3], custom_nan[0]])
>>> exmpl
array([0.1, 1.2, nan, nan])
>>> which_nan(exmpl)
array([-1, -1,  3,  0], dtype=int64)
  

Возможно, удивительно, что это, похоже, выдерживает по крайней мере некоторые базовые операции numpy:

 >>> which_nan(np.sin(exmpl))
array([-1, -1,  3,  0], dtype=int64)
  

Ответ №4:

В приведенном ниже вопросе в комментарии я спрашиваю, почему бы не использовать np.inf , -np.inf и np.nan , и автор отвечает, что это то, что ему нужно.

Поэтому я добавляю post, потому что люди чаще смотрят на ответы, а не на комментарии.

Комментарии:

1. Хотя я думаю, что мой ответ лучше в общем случае, это, вероятно, лучше для данного конкретного случая, 1

2. Я не уверен. Автор пишет «Данные большие, и скорость чтения важна». Таким образом, добавление массивов svereal 0,1 не является хорошей идеей. Я думаю, что добавить один массив с разными метками (1,2,3, …) лучше, чем несколько массивов.

3. Да, как я уже сказал, ваша лучше подходит для данного конкретного случая, но может быть случай, когда np.inf также может быть фактическое значение, тогда вам понадобится другое решение

4. Это обеспечивает достаточную гибкость для моих нужд, и, вероятно, это не помешает будущим реализациям. Для кого-то, кому нужно больше версий, решение @PaulPanzer, вероятно, было бы решением, но в моем невежестве я бы, вероятно, сказал, что это не так безопасно.

Ответ №5:

Было предложено создать три разных object экземпляра для каждого вашего случая.

Поскольку вы хотите, чтобы эти объекты обладали свойствами NaN , вы можете попробовать создать три разных NaN экземпляра.

 NOT_APPLICABLE = float("nan")
NOT_MEASURED = float("nan")
UNKNOWN = float("nan")
  

Это на грани взлома, поэтому используйте на свой страх и риск, но я не верю, что какая-либо реализация Python оптимизирует NaN , чтобы всегда повторно использовать один и тот же объект. Тем не менее, вы можете добавить контрольное условие, чтобы проверить это перед запуском.

 if NOT_APPLICABLE is NOT_MEASURED or NOT_MEASURED is UNKNOWN or UNKNOWN is NOT_APPLICABLE :
    raise ValueError # or try something else
  

Если это сработает, то преимущество этого заключается в том, что вы можете сравнить NaN идентификатор, чтобы проверить его значение.

 row = [1.0, 2.4, UNKNOWN]

...

if value is UNKNOWN:
    ...
  

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

Раскрытие информации: это хакерское предложение, мне не терпится услышать об этом от других.

Комментарии:

1. Принимаю эту, потому что sentinel сильно ее улучшает.