Как dtype массива numpy рассчитывается внутри?

Я просто возился с массивами numpy, когда понял менее известное поведение параметра dtypes.

Кажется, что он меняется при изменении ввода. Например,

t = np.array([2, 2])
t.dtype

дает dtype('int32')

Однако,

t = np.array([2, 22222222222])
t.dtype

дает dtype('int64')

Итак, мой первый вопрос: как это рассчитывается? Делает ли это тип данных подходящим для максимального элемента в качестве типа данных для всех элементов? Если это так, не думаете ли вы, что для этого требуется больше места, потому что он излишне хранит избыточную память для хранения 2 во втором массиве как 64-битное целое число?

Теперь снова, если я хочу изменить нулевой элемент array([2, 2]) как

t = np.array([2, 2])
t[0] = 222222222222222

Я получаю OverflowError: Python int too large to convert to C long.

Мой второй вопрос: почему он не поддерживает ту же логику, что и при создании массива, если вы измените определенное значение? Почему он не пересчитывает и не переоценивает?

Любая помощь приветствуется. Заранее спасибо.

Я считаю, что int32 является значением по умолчанию dtype для целых чисел в numpy. Однако 22222222222 больше, чем 2**32//2-1 (максимальное значение для int32), поэтому вместо него используется int64.

Brenlla 15.03.2019 12:46

После создания dtype массива фиксируется. Установленные значения изменяются, чтобы соответствовать этому dtype, если это возможно. view и astype создают новые массивы. np.array — сложная функция, способная обрабатывать самые разные входные данные. Он оценивает все входные значения и выбирает тип dtype, который подходит для всех. Большинство из нас принимает этот выбор как операцию черного ящика — результаты обычно логичны.

hpaulj 15.03.2019 17:31

@hpaulj «Большинство из нас принимают этот выбор как операцию черного ящика». Вы только что сделали мой день :-)

Paul Panzer 15.03.2019 18:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
3
1 028
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Давайте попробуем найти соответствующие биты в документах.

из строки документа np.array:

array(...)

[...]

Parameters

[...]

dtype : data-type, optional The desired data-type for the array. If not given, then the type will be determined as the minimum type required to hold the objects in the sequence. This argument can only be used to 'upcast' the array. For downcasting, use the .astype(t) method.

[...]

(мой акцент)

Следует отметить, что это не совсем точно, например, для целочисленных массивов целое число системы (C) по умолчанию предпочтительнее, чем меньшие целочисленные типы, как видно из вашего примера.

Обратите внимание, что для быстрой работы numpy важно, чтобы все элементы массива были одного размера. Иначе как бы вы быстро нашли, скажем, 1000-й элемент? Кроме того, смешивание типов не сэкономит столько места, поскольку вам придется хранить типы каждого отдельного элемента поверх необработанных данных.

Повторяю ваш второй вопрос. Прежде всего. В numpy есть правила продвижения типов. Лучший документ, который я смог найти для этого, — это строка документа np.result_type:

result_type(...) result_type(*arrays_and_dtypes)

Returns the type that results from applying the NumPy type promotion rules to the arguments.

Type promotion in NumPy works similarly to the rules in languages like C++, with some slight differences. When both scalars and arrays are used, the array's type takes precedence and the actual value of the scalar is taken into account.

For example, calculating 3*a, where a is an array of 32-bit floats, intuitively should result in a 32-bit float output. If the 3 is a 32-bit integer, the NumPy rules indicate it can't convert losslessly into a 32-bit float, so a 64-bit float should be the result type. By examining the value of the constant, '3', we see that it fits in an 8-bit integer, which can be cast losslessly into the 32-bit float.

[...]

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

Точный способ применения этих правил сложен и, по-видимому, представляет собой компромисс между интуитивностью и эффективностью.

Например, выбор основан на входных данных, а не на результате.

>>> A = np.full((2, 2), 30000, 'i2')
>>> 
>>> A
array([[30000, 30000],
       [30000, 30000]], dtype=int16)
# 1
>>> A + 30000
array([[-5536, -5536],
       [-5536, -5536]], dtype=int16)
# 2
>>> A + 60000
array([[90000, 90000],
       [90000, 90000]], dtype=int32)

Здесь побеждает эффективность. Возможно, было бы более интуитивно понятно, если бы №1 вел себя как №2. Но это будет дорого.

Кроме того, и это более непосредственно связано с вашим вопросом, продвижение типа применяется только вне места, а не на месте:

# out-of-place
>>> A_new = A + 60000
>>> A_new
array([[90000, 90000],
       [90000, 90000]], dtype=int32)
# in-place
>>> A += 60000
>>> A
array([[24464, 24464],
       [24464, 24464]], dtype=int16)

или

# out-of-place
>>> A_new = np.where([[0, 0], [0, 1]], 60000, A)
>>> A_new
array([[30000, 30000],
       [30000, 60000]], dtype=int32)
# in-place
>>> A[1, 1] = 60000
>>> A
array([[30000, 30000],
       [30000, -5536]], dtype=int16)

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

И они должны ответить на ваш второй вопрос:

Переход на больший тип dtype потребует выделения большего буфера и копирования всех данных. Мало того, что это было бы дорого для больших массивов.

Многие идиомы в numpy полагаются на представления и тот факт, что запись в представление напрямую изменяет базовый массив (и другие перекрывающиеся представления). Следовательно, массив не может свободно изменять свой буфер данных, когда захочет. Чтобы не разорвать связь между представлениями, массиву необходимо знать обо всех представлениях в своем буфере данных, что добавит много административной нагрузки, и все эти представления также должны будут изменить свои указатели данных и метаданные. И если первый массив сам является представлением (скажем, фрагментом) в другой массив, все становится еще хуже.

Я полагаю, мы можем согласиться с тем, что это того не стоит, и поэтому типы не продвигаются на месте.

Другие вопросы по теме