Pandas version checks

  • [X] I have checked that this issue has not already been reported.

  • [X] I have confirmed this bug exists on the latest version of pandas.

  • [ ] I have confirmed this bug exists on the main branch of pandas.

Reproducible Example

import numpy as np
import pandas as pd

N = 10
hash_dtype = np.dtype([(f'h{i}', np.uint64) for i in range(4)])
hashes = np.zeros(N, dtype=hash_dtype)

df = pd.DataFrame(data={'hashes':hashes, 'other_stuff':np.zeros(N)})
idx = [0, 1]

## This fails
df.loc[idx, 'hashes'] = np.ones(len(idx), dtype=hash_dtype)
# LossySetitemError: 
# During handling of the above exception, another exception occurred:
# AssertionError
# AssertionError: Something has gone wrong, please report a bug at https://github.com/pandas-dev/pandas/issues

## This also files
print(df)
# 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''

## This is ok
print(df['other_stuff'])

## This is a partial workaround
df['hashes'] = np.zeros(len(df), dtype=hash_dtype) # same errors with or without this line
df['hashes'].values[idx] = np.ones(len(idx), dtype=hash_dtype)

## But things still broken
print(df['hashes'])
# 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''

## This works
print(df.loc[0, 'hashes'])

## This doesn't
print(df.loc[:, 'hashes'])
# 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''

## OK
x = df['hashes']

## Not OK
x = df[['hashes', 'other_stuff']]
# TypeError: void() takes at least 1 positional argument (0 given)

Issue Description

Working with a numpy array with a structured type consisting of four 64 bit integers, there are various errors when getting/setting the associated data in a dataframe.

  • Trying to set values with df.loc[idx, 'hashes'] = get LossySetitemError, AssertionError: Something has gone wrong
  • can work-around by directly indexing into the underlying array df['hashes'].values[idx] = (but for other indexes would have to do something like df['hashes'].values[df.index.isin(idx)] = or use df.index.get_indexer)
  • printing/string reps broken, e.g. print(df) and print(df['hashes'])
  • trying to access df[['hashes', 'other_stuff']] TypeError: void() takes at least 1 positional argument (0 given)

Expected Behavior

can set values with .loc without assertion error can print dataframe without exception

Installed Versions

INSTALLED VERSIONS ------------------ commit : 0691c5cf90477d3503834d983f69350f250a6ff7 python : 3.12.7 python-bits : 64 OS : Linux OS-release : 6.6.57-1-lts Version : #1 SMP PREEMPT_DYNAMIC Thu, 17 Oct 2024 13:57:25 +0000 machine : x86_64 processor : byteorder : little LC_ALL : None LANG : en_US.UTF-8 LOCALE : en_US.UTF-8 pandas : 2.2.3 numpy : 2.1.2 pytz : 2024.2 dateutil : 2.9.0.post0 pip : 24.2 Cython : 3.0.11 sphinx : None IPython : 8.28.0 adbc-driver-postgresql: None adbc-driver-sqlite : None bs4 : 4.12.3 blosc : None bottleneck : None dataframe-api-compat : None fastparquet : None fsspec : None html5lib : None hypothesis : None gcsfs : None jinja2 : None lxml.etree : 5.3.0 matplotlib : 3.9.2 numba : None numexpr : None odfpy : None openpyxl : 3.1.5 pandas_gbq : None psycopg2 : None pymysql : None pyarrow : None pyreadstat : None pytest : None python-calamine : None pyxlsb : None s3fs : None scipy : 1.14.1 sqlalchemy : None tables : None tabulate : None xarray : None xlrd : 2.0.1 xlsxwriter : 3.1.9 zstandard : 0.22.0 tzdata : 2024.2 qtpy : 2.4.1 pyqt5 : None

Comment From: digitalsignalperson

also worth mentioning the problems go away if you allow the structured array to have an object dtype

e.g.

import numpy as np
import pandas as pd

N = 10
df = pd.DataFrame(data={'other_stuff':np.zeros(N)})

idx = [0, 1]
hash_dtype = np.dtype([(f'h{i}', np.uint64) for i in range(4)])
df.loc[idx, 'hashes'] = np.ones(len(idx), dtype=hash_dtype)
# here df['hashes'] coerced to object data type

Comment From: rhshadrach

Thanks for the report. Indeed, pandas does not support NumPy structured arrays. I do not think it is feasible to support these.

Comment From: jorisvandenbossche

Should we rather error when a user creates such a Series with an unsupported dtype, instead of allowing to create it but then fail later on in various confusing ways?

Or is there enough that works that people would want to use a Series/DataFrame container with such data?

Comment From: digitalsignalperson

For sure it would be helpful to fail earlier, it took me a while to figure out what led to creating this issue.

My workaround was to view the numpy array as bytes. In my example the structured type is 32 bytes and .astype('S32') makes each element appear as a 32 bytes string. Pandas seems fine with this data type.

Is there any trick where pandas could do a similar thing, just treat structured arrays as opaque "element is N bytes" or even internally storing as a np.dtype(f'S{element width') type?

Comment From: rhshadrach

I'm +1 on failing in the constructor.

Is there any trick where pandas could do a similar thing, just treat structured arrays as opaque "element is N bytes" or even internally storing as a np.dtype(f'S{element width') type?

I don't think this should happen silently.

Comment From: digitalsignalperson

I don't think this should happen silently.

I was thinking more in terms of something pandas could do internally, but I don't know the internals for why it trips on structured types or if this is a practical idea.

For example maybe some bookkeeping happens so that internally pandas sees a ndarray.view of dtype('S{item size}') but to the user it appears like their custom data type.

N = 10
hash_dtype = np.dtype([(f'h{i}', np.uint64) for i in range(4)])
hashes = np.zeros(N, dtype=hash_dtype)

hashes_view = hashes.view(f'S{hash_dtype.itemsize}') 
hashes[0] = (1, 1, 1, 1)

In [35]: hashes
Out[35]: 
array([(1, 1, 1, 1), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0),
       (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0),
       (0, 0, 0, 0), (0, 0, 0, 0)],
      dtype=[('h0', '<u8'), ('h1', '<u8'), ('h2', '<u8'), ('h3', '<u8')])

In [37]: print(hashes_view)
[b'\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01'
 b'' b'' b'' b'' b'' b'' b'' b'' b'']

Comment From: rhshadrach

For example maybe some bookkeeping happens so that internally pandas sees a ndarray.view of dtype('S{item size}') but to the user it appears like their custom data type.

If this were possible, would essentially any operation other than getting, setting, and reshaping just raise? E.g. groupby.sum. I'm quite negative on partial support for a dtype like this.

Comment From: rhshadrach

Similar to #55011 I think

Comment From: jorisvandenbossche

Another similar bug report in https://github.com/pandas-dev/pandas/issues/42739, with a masked structured array, where for this case the DataFrame constructor already fails, although it could use a more informative error message.

import numpy as np
import pandas as pd
import numpy.ma as ma

# create a masked, structured array
a = np.ma.array([(1, 2.2), (42, 5.5)],
                dtype=[('a',int),('b',float)],
                mask=[(True,False),(False,True)])

b = pd.DataFrame(a)

Currently gives TypeError: Iterator operand 1 dtype could not be cast from dtype([('a', '?'), ('b', '?')]) to dtype('bool') according to the rule 'unsafe'

Comment From: rhshadrach

We should also fail on a NumPy bytes array in the constructor, e.g.

pd.Series(np.array([b""]))