pandas-dev / pandas

Flexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more
https://pandas.pydata.org
BSD 3-Clause "New" or "Revised" License
43.19k stars 17.77k forks source link

BUG: Series.mul silently returns wrong values with `UInt8` dtype when overflowing the max value range #59261

Open Timost opened 1 month ago

Timost commented 1 month ago

Pandas version checks

Reproducible Example

import pandas as pd
s = pd.Series([36], dtype='UInt8')
100 * s # This returns a series with one value: 16 (!!)
s.mul(100) # Also returns a wrong value

# But
7 * s # This returns the correct value

8 * s # This returns the wrong value (overflow)

100. * s # returns the correct value (due to casting I guess)

Issue Description

When multiplying a UInt8 series by a python integer, for which the result overflows the dtype range, the resulting values are wrong.

AFAICT this only happens with the UInt8 dtype (I tested UInt8, UInt16, UInt32 and UInt64). Other UInt dtypes are properly casted to the "next" dtype.

Anybody computing percentages on UInt8 dtypes will easily hit this issue.

This was introduced in the 2.1.0 Release.

Expected Behavior

I expect the series to be casted to UInt16 when the results overflow the UInt8 range and return the correct values.

Installed Versions

INSTALLED VERSIONS ------------------ commit : fd3f57170aa1af588ba877e8e28c158a20a4886d python : 3.11.6.final.0 python-bits : 64 OS : Linux OS-release : 5.15.153.1-microsoft-standard-WSL2 Version : #1 SMP Fri Mar 29 23:14:13 UTC 2024 machine : x86_64 processor : x86_64 byteorder : little LC_ALL : None LANG : C.UTF-8 LOCALE : en_US.UTF-8 pandas : 2.2.0 numpy : 1.26.4 pytz : 2024.1 dateutil : 2.9.0.post0 setuptools : 70.3.0 pip : 23.3.1 Cython : None pytest : 7.4.4 hypothesis : None sphinx : 7.3.7 blosc : None feather : None xlsxwriter : None lxml.etree : None html5lib : None pymysql : 1.4.6 psycopg2 : None jinja2 : None IPython : 8.26.0 pandas_datareader : None adbc-driver-postgresql: None adbc-driver-sqlite : None bs4 : 4.12.3 bottleneck : None dataframe-api-compat : None fastparquet : None fsspec : 2024.6.1 gcsfs : None matplotlib : 3.9.1 numba : None numexpr : None odfpy : None openpyxl : 3.1.5 pandas_gbq : None pyarrow : 16.1.0 pyreadstat : None python-calamine : None pyxlsb : None s3fs : 2024.6.1 scipy : None sqlalchemy : 2.0.31 tables : None tabulate : None xarray : None xlrd : None zstandard : None tzdata : 2024.1 qtpy : None pyqt5 : None
simran2jain commented 1 month ago

take

BobChuckyJoe commented 1 month ago

Hi, wouldn't 16 be the correct value returned for 100 * 36 mod 256 ?

Timost commented 1 month ago

Hi,

yes, 16 is the value corresponding to the overflow, so in a sense it is correct.

However, AFAICT, other UInt dtypes do not behave this way and are casted to the "next bigger" dtype when computation results overflow.

I think the behavior should be coherent across the various UInt flavours.

rhshadrach commented 3 weeks ago

I believe pandas will overflow if integer being multiplied can be stored in the given dtype, but otherwise will upcast.

s = pd.Series([36], dtype='Int16')
print((s * 1000).dtype, (s * 1000).iloc[0])
# Int16 -29536
print((s * 32767).dtype, (s * 32767).iloc[0])
# Int16 -36
print((s * 32768).dtype, (s * 32768).iloc[0])
# Int32 1179648