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.

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

Reproducible Example

import pandas as pd
  # To demonstrate the bug we need to load the pint library
from pint import Quantity as Q_
  # Create a two-element DataFrame with Quanities
qq = pd.DataFrame({'qq': [Q_(1.0,'m'), Q_(2.0, 'm')]})
print(qq)

           qq
 0  1.0 meter
 1  2.0 meter


  # Attempt to replace the first element with a new Quantity
qq.iloc[0, 0] = Q_(3.0,'m')
  # This will fail with TypeError: object of type 'float' has no len()

Issue Description

The issue concerns a change made 9 months ago: https://github.com/pandas-dev/pandas/commit/63ae9aac0555009b2e83bdb7a764d51c72929c90#diff-55001624a0932c1b6cee2e6ddb65dea85c1faf0dee84812c0ca0c32916a71438

Previously, the conditional logic sought to test whether the right hand side was scalar or not, which had one problem. It was changed to use lib.is_list_like, which creates the problem (actually two problems) we see today.

I have also discussed with the Pint people here: https://github.com/hgrecco/pint/discussions/1617 It's quite possible to real problem is with the cython code implementing is_list_like, which does not obey the Python standard for checking whether something is iterable (and therefore list-like).

Expected Behavior

The expected behavior is that Pandas would replace the first element of the DataFrame with the new value, resulting in the following DataFrame:

          qq
 0  3.0 meter
 1  2.0 meter

Because pandas/core/dtypes/interence.py implements is_list_like as follows:

is_list_like = lib.is_list_like

It should be possible for me to work around this as follows:

from pandas.core.dtypes.inference import is_list_like
from pandas._libs import lib

def new_is_list_like(x):
    if lib.is_list_like(x):
        try:
            iter(x)
            return True
        except TypeError:
            return False
    return False

Here you can see the specific confusion of treating a scalar Pint quantity as list-like:

>>> is_list_like(Q_(1, 'm'))
True
>>> new_is_list_like(Q_(1, 'm'))
False
>>> is_list_like = new_is_list_like
>>> is_list_like(Q_(1, 'm'))
False
>>> qq.iloc[0, 0] = Q_(3.0,'m')

That last line should have given me the expected behavior, but the patch calls lib.is_list_like instead of is_list_like, defeating the work-around.

Installed Versions

>>> pd.show_versions() /Users/michael/opt/miniconda3/envs/pint-vanilla/lib/python3.9/site-packages/_distutils_hack/__init__.py:33: UserWarning: Setuptools is replacing distutils. warnings.warn("Setuptools is replacing distutils.") INSTALLED VERSIONS ------------------ commit : 91111fd99898d9dcaa6bf6bedb662db4108da6e6 python : 3.9.13.final.0 python-bits : 64 OS : Darwin OS-release : 21.6.0 Version : Darwin Kernel Version 21.6.0: Mon Aug 22 20:17:10 PDT 2022; root:xnu-8020.140.49~2/RELEASE_X86_64 machine : x86_64 processor : i386 byteorder : little LC_ALL : None LANG : en_US.UTF-8 LOCALE : en_US.UTF-8 pandas : 1.5.1 numpy : 1.23.4 pytz : 2022.5 dateutil : 2.8.2 setuptools : 63.4.1 pip : 22.2.2 Cython : 0.29.32 pytest : None hypothesis : None sphinx : None blosc : None feather : None xlsxwriter : None lxml.etree : None html5lib : None pymysql : None psycopg2 : None jinja2 : None IPython : None pandas_datareader: None bs4 : None bottleneck : None brotli : None fastparquet : None fsspec : None gcsfs : None matplotlib : None numba : None numexpr : None odfpy : None openpyxl : None pandas_gbq : None pyarrow : None pyreadstat : None pyxlsb : None s3fs : None scipy : None snappy : None sqlalchemy : None tables : None tabulate : None xarray : None xlrd : None xlwt : None zstandard : None tzdata : None

Comment From: jbrockmendel

"still" suggests this has been discussed before. is there another issue for this?

Comment From: andrewgsavage

On latest pint-pandas this is not an issue (it may not have before). You need to specify pint dtype to make this work.

``` import pandas as pd import pint import pint_pandas from pint import Quantity as Q_

qq = pd.DataFrame({'qq': [Q_(1.0,'m'), Q_(2.0, 'm')]}, dtype ="pint[m]") print(qq)

qq

0 1.0 1 2.0

# Attempt to replace the first element with a new Quantity qq.iloc[0, 0] = Q_(3.0,'m') print(qq)

qq

0 3.0 1 2.0```

Comment From: topper-123

I just tried this and it works as expected. Thanks for the report, @MichaelTiemannOSC