Это может быть ошибка или что-то, чего я не понимаю, когда numpy решает преобразовать типы объектов в массив «объектов».
X = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [1158941147679947299,0]
Y = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [11589411476799472995,0]
Z = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [115894114767994729956,0]
print(type(X[0]),X[0]) # <class 'int'> 7047216832217320738
print(type(Y[0]),Y[0]) # <class 'float'> 1.7477687161336848e+19
print(type(Z[0]),Z[0]) # <class 'int'> 121782390452532103395
Сами массивы остаются типа «объект» (как и ожидалось). Неожиданно объекты массива Y
были преобразованы в «плавающие». Почему это происходит? В результате я сразу же теряю точность в своей комбинаторике. Чтобы сделать ситуацию еще более странной, удаление 0
исправляет ситуацию:
X = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [1158941147679947299]
Y = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [11589411476799472995]
Z = np.array([5888275684537373439, 1945629710750298993],dtype=object) + [115894114767994729956]
print(type(X[0]),X[0]) # <class 'int'> 7047216832217320738
print(type(Y[0]),Y[0]) # <class 'int'> 17477687161336846434
print(type(Z[0]),Z[0]) # <class 'int'> 121782390452532103395
Я пробовал другие вещи, например, использовать большие/меньшие числа, но редко (если вообще когда-либо) получал «плавающие числа». Это что-то очень специфическое в размере этих конкретных значений «int».
Лучший код, показывающий проблему.
import numpy as np
A = np.array([1,1],dtype=object) + [2**62,0]
B = np.array([1,1],dtype=object) + [2**63,0]
C = np.array([1,1],dtype=object) + [2**64,0]
D = np.array([1,1],dtype=object) + [2**63]
E = np.array([1,1],dtype=object) + [2**63,2**63]
print(type(A[0]),A[0]) # <class 'int'> 4611686018427387905
print(type(B[0]),B[0]) # <class 'float'> 9.223372036854776e+18
print(type(C[0]),C[0]) # <class 'int'> 18446744073709551617
print(type(D[0]),D[0]) # <class 'int'> 9223372036854775809
print(type(E[0]),E[0]) # <class 'int'> 9223372036854775809
@hpaulj Но обычные числовые массивы нельзя использовать, если используются целые числа с несколькими сотнями цифр. Обновлено: добавлен лучший пример, который показывает, что он не выполняет преобразование, поскольку [2**63,0]
не работает, но [2**63,2**63]
работает.
Я предполагаю, что это ошибка, а не функция. Я пошел дальше и опубликовал это github.com/numpy/numpy/issues/26818
Не можете ли вы явно указать dtype объекта для второго списка/массива? Скажите ему именно то, что вы хотите.
@hpaulj Боже мой, да!! Это работает, И мне не нужно менять весь свой код на циклы for. Гений. Если вы хотите опубликовать это, я приму это.
Если значение соответствует int64
, это int
. Но как только оно превышает это значение, NumPy переключается на float
, пока не превысит это значение в два раза, а затем переключается обратно на int
, поскольку Python int
имеет произвольную точность.
import numpy as np
import sys
a = np.array([0, 0], dtype=object) + [sys.maxsize, 0]
b = np.array([0, 0], dtype=object) + [sys.maxsize + 1, 0]
c = np.array([0, 0], dtype=object) + [(2 * sys.maxsize) + 1, 0]
d = np.array([0, 0], dtype=object) + [2 * (sys.maxsize + 1), 0]
print(sys.maxsize)
for x in [a, b, c, d]:
print(type(x[0]), ':', x[0])
Выход:
9223372036854775807
<class 'int'> : 9223372036854775807
<class 'float'> : 9.223372036854776e+18
<class 'float'> : 1.8446744073709552e+19
<class 'int'> : 18446744073709551616
Вы можете считать это ошибкой (или просто нежелательным), что numpy выбирает int
вместо np.int64
в качестве типа для a
(и X
), но вполне может быть причина, по которой это происходит по выбору, поскольку операции над значением могут вызвать переполнение, и вы это сделали уточните object
.
Учитывать:
a = np.array([0, 0], dtype=np.int64) + [sys.maxsize, 0]
b = np.array([0, 0], dtype=np.int64) + [sys.maxsize + 1, 0]
c = np.array([0, 0], dtype=np.int64) + [(2 * sys.maxsize) + 1, 0]
d = np.array([0, 0], dtype=np.int64) + [2 * (sys.maxsize + 1), 0]
for x in [a, b, c, d]:
print(type(x[0]), ':', x[0])
Выход:
<class 'numpy.int64'> : 9223372036854775807
<class 'numpy.float64'> : 9.223372036854776e+18
<class 'numpy.float64'> : 1.8446744073709552e+19
<class 'int'> : 18446744073709551616
Что может быть ближе к тому, что вы ожидали.
Обратите внимание, что суммы в вашем примере выглядят следующим образом:
7047216832217320738 # your X[0] < sys.maxsize
9223372036854775807 # sys.maxsize
17477687161336846434 # your Y[0] > sys.maxsize
18446744073709551616 # 2 * (sys.maxsize + 1)
121782390452532103395 # your Z[0] > 2 * (sys.maxsize + 1)
Также обратите внимание, что значение, которое вы добавляете, и сумма имеют значение:
import numpy as np
import sys
a = np.array([0, 0], dtype=np.int64) + [(2 * sys.maxsize) + 1, 0]
b = np.array([0, 0], dtype=np.int64) + [2 * (sys.maxsize + 1), 0]
c = np.array([sys.maxsize, 0], dtype=np.int64) + [(2 * sys.maxsize) + 1, 0]
d = np.array([sys.maxsize, 0], dtype=np.int64) + [2 * (sys.maxsize + 1), 0]
try:
# compare e to c
e = np.array([sys.maxsize + 1, 0], dtype=np.int64) + [(2 * sys.maxsize) + 1, 0]
except OverflowError as e:
print(e)
for x in [a, b, c, d]:
print(type(x[0]), ':', x[0])
Выход:
int too big to convert
<class 'numpy.float64'> : 1.8446744073709552e+19
<class 'int'> : 18446744073709551616
<class 'numpy.float64'> : 2.7670116110564327e+19
<class 'int'> : 27670116110564327423
Добавление (2 * sys.maxsize) + 1
к sys.maxsize
по-прежнему приводит к numpy.float64
, но попытка добавить (2 * sys.maxsize) + 1
к sys.maxsize + 1
приводит к переполнению.
Я согласен, что это, по-видимому, пороговые значения. Однако ваше объяснение, похоже, предполагает, что A + [2**63,0]
должно вести себя так же, как A + 2**63
или A + [2**63,2**63]
, но это не так. Таким образом, дело не только в том, что numpy имеет определенные пороговые значения, которые используются, но и в том, что numpy непоследователен, когда эти пороговые значения применяются (если это ожидаемая функция).
Еще одно замечание. Насколько я понимаю, dtype=object
— это способ заполнить массив numpy указателями вместо целых чисел, чисел с плавающей запятой или чего-то еще. Эти указатели могут быть ЛЮБЫМ объектом Python, и, как правило, numpy будет зависеть от того, как ведет себя этот объект Python. В целом непоследовательно, чтобы numpy приступил к преобразованию типов «объектов» вместо того, чтобы делать то, что ему говорят объекты. Следовательно, A + 1
должен вести себя аналогично циклу for, если объекты в A являются объектами Python. Вот почему массив объектов может содержать 1000-значные целые числа и выполнять с ними модульную арифметику.
Вы правы, отмечая различное поведение, которое сбивает с толку и, возможно, в некоторых случаях нежелательно. Я хотел указать на конкретные границы и на то, как избежать, казалось бы, произвольного поведения. Идея «указателей» здесь мутит воду. Если вас волнует тип, не стоит уточнять object
— то, что там делает NumPy, соответствует, хотя я согласен, что результат может быть нежелательным. Почему NumPy выбирает тот или иной вариант, скорее всего, связано с внутренней оптимизацией для конкретных случаев использования, и лучше всего исследовать его исходный код и, возможно, документацию — я не стал искать причину.
Ключевой момент, на который следует обратить внимание: передача object
в качестве типа не должна рассматриваться как «сохранить тот тип, который я вам дал». Если вы специально хотите сохранить переданный тип, NumPy не позволит вам — вместо этого вы можете самостоятельно установить тип на основе какого-либо условия.
Я не уверен в вашем желании избегать типов object
. Их суть в том, что такие вещи, как np.array(['thing','other'],dtype=object) + 's'
, имеют смысл и работают. Причина в том, что numpy подчиняется использованию объекта +
. Pandas, как и многие другие модули, наполнены такого рода организационным поведением. В комбинаторике следует ожидать, что такие вещи, как np.array([2**100,3**100],dtype=object) % 1234
, будут работать (как они и работают в настоящее время, используя произвольные встроенные целые числа Python). Фактически, при создании массива размером более int64 происходит автоматическое приведение к типу object
.
Ошибка, описанная выше, ничем не отличалась бы от того, если бы мои строки, помещенные в массив numpy, вместо этого были преобразованы в кортежи с некоторыми произвольными порогами сложения длины строки.
Я думаю, что «ошибка» — это не столько ошибка, сколько запланированная функция: StackOverflow — не место для обсуждения проектных решений в сторонних библиотеках. Я понимаю вашу точку зрения и советую вам сообщить о проблеме разработчикам. Но я бы порекомендовал обосновать обнаруженный вами недостаток конструкции, поскольку я не думаю, что это «ошибка», поскольку она не противоречит спецификации.
Тем не менее, я думаю, что ответил на вопрос «почему» так хорошо, как и можно было ожидать (по сути, это первая строка моего ответа). Я не могу решить, что вы не согласны с причиной (и это не самое подходящее место).
Я ценю ваш анализ и обсуждение. И я согласен, что никогда бы не опубликовал это, если бы с самого начала был убежден, что это ошибка, и ждал отзывов. Однако после рассмотрения нескольких примеров с большими целыми числами, строками, смешанными типами объектов Python и т. д. кажется гораздо более очевидным, что это «ошибка», а не задуманная.
In [323]: X = np.array([5888275684537373439, 1945629710750298993],dtype=object)
Случай 1 — не слишком большое целое число во втором аргументе:
In [324]: X+[1158941147679947299,0]
Out[324]: array([7047216832217320738, 1945629710750298993], dtype=object)
То же самое, если мы явно создадим массив объектов:
In [325]: X+np.array([1158941147679947299,0],object)
Out[325]: array([7047216832217320738, 1945629710750298993], dtype=object)
2-й случай — преобразование в числа с плавающей запятой:
In [326]: X+[11589411476799472995,0]
Out[326]: array([1.7477687161336848e+19, 1.945629710750299e+18], dtype=object)
Опять же, с явным объектом все в порядке:
In [327]: X+np.array([11589411476799472995,0],object)
Out[327]: array([17477687161336846434, 1945629710750298993], dtype=object)
Преобразование списка в массив без спецификации dtype создает число с плавающей запятой, которое распространяется по сумме:
In [328]: np.array([11589411476799472995,0])
Out[328]: array([1.15894115e+19, 0.00000000e+00])
где в первом случае достаточно мало, чтобы быть int64:
In [329]: np.array([1158941147679947299,0])
Out[329]: array([1158941147679947299, 0], dtype=int64)
третий случай - оставшееся int:
In [330]: X+[115894114767994729956,0]
Out[330]: array([121782390452532103395, 1945629710750298993], dtype=object)
In [331]: X+np.array([115894114767994729956,0],object)
Out[331]: array([121782390452532103395, 1945629710750298993], dtype=object)
Это достаточно большой размер, чтобы оставаться объектным типом:
In [332]: np.array([115894114767994729956,0])
Out[332]: array([115894114767994729956, 0], dtype=object)
Таким образом, ключевое отличие заключается в том, как список преобразуется в массив. Object dtype — это запасной вариант, который используется, когда невозможно создать «обычный» числовой массив. Вы всегда должны предполагать, что object
математика dtype — это «приемный ребенок», что-то, что выбрано на втором месте.
Второй случай без 0 — это другой dtype
:
In [334]: np.array([11589411476799472995])
Out[334]: array([11589411476799472995], dtype=uint64)
Никогда не разумно делать предположения о том, когда список преобразуется в массив dtype объекта. Если эта функция важна, сделайте ее явной!
Ваш ответ очень полезен. Однако я не согласен с вашей оценкой того, что пользователь должен знать, что «dtype — это «приемный ребенок»». Проблема не в том, что пользователь предполагает согласованность с A+[2**62,0]
, A+[2**63,0]
, A+[2**64,0]
, A+[2**62,2**62]
, A+[2**63,2**63]
, A+[2**64,2**64]
, A+2**62
, A+2**63
, A+2**64
. Скорее, numpy должен относиться к этому по-другому. Либо выдайте ошибку/предупреждение, либо сделайте все списки одинаковыми, либо сделайте все большие целые числа одинаковыми, либо сделайте все маленькие целые числа одинаковыми. Здесь у нас ничего этого нет.
Скорее всего,
[1158941147679947299,0]
преобразуется в массивint
dtype и контролирует добавление.[1158941147679947299]
может оставаться скаляром Python. Математические операции с массивами dtype объектов являются случайными, не так четко определены и последовательны, как обычные числовые массивы.