Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/pandas/pyproject.toml +811 -0
- lib/python3.12/site-packages/pandas/testing.py +18 -0
- lib/python3.12/site-packages/pandas/tests/__init__.py +0 -0
- lib/python3.12/site-packages/pandas/tests/computation/__init__.py +0 -0
- lib/python3.12/site-packages/pandas/tests/computation/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/computation/__pycache__/test_compat.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/computation/test_compat.py +32 -0
- lib/python3.12/site-packages/pandas/tests/computation/test_eval.py +2001 -0
- lib/python3.12/site-packages/pandas/tests/indexes/__init__.py +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__init__.py +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_append.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_astype.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_category.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_constructors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_equals.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_fillna.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_formats.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_indexing.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_map.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_reindex.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_setops.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_append.py +62 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_astype.py +90 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_category.py +394 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_constructors.py +142 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_equals.py +96 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_fillna.py +54 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_formats.py +120 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_indexing.py +420 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_map.py +144 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_reindex.py +78 -0
- lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_setops.py +18 -0
- lib/python3.12/site-packages/pandas/tests/indexes/conftest.py +41 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__init__.py +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_arithmetic.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_constructors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_date_range.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_datetime.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_formats.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_freq_attr.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_indexing.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_iter.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_join.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_npfuncs.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_ops.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_partial_slicing.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_pickle.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_reindex.cpython-312.pyc +0 -0
lib/python3.12/site-packages/pandas/pyproject.toml
ADDED
|
@@ -0,0 +1,811 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
# Minimum requirements for the build system to execute.
|
| 3 |
+
# See https://github.com/scipy/scipy/pull/12940 for the AIX issue.
|
| 4 |
+
requires = [
|
| 5 |
+
"meson-python==0.13.1",
|
| 6 |
+
"meson==1.2.1",
|
| 7 |
+
"wheel",
|
| 8 |
+
"Cython~=3.0.5", # Note: sync with setup.py, environment.yml and asv.conf.json
|
| 9 |
+
# Force numpy higher than 2.0, so that built wheels are compatible
|
| 10 |
+
# with both numpy 1 and 2
|
| 11 |
+
"numpy>=2.0",
|
| 12 |
+
"versioneer[toml]"
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
build-backend = "mesonpy"
|
| 16 |
+
|
| 17 |
+
[project]
|
| 18 |
+
name = 'pandas'
|
| 19 |
+
dynamic = [
|
| 20 |
+
'version'
|
| 21 |
+
]
|
| 22 |
+
description = 'Powerful data structures for data analysis, time series, and statistics'
|
| 23 |
+
readme = 'README.md'
|
| 24 |
+
authors = [
|
| 25 |
+
{ name = 'The Pandas Development Team', email='pandas-dev@python.org' },
|
| 26 |
+
]
|
| 27 |
+
license = {file = 'LICENSE'}
|
| 28 |
+
requires-python = '>=3.9'
|
| 29 |
+
dependencies = [
|
| 30 |
+
"numpy>=1.22.4; python_version<'3.11'",
|
| 31 |
+
"numpy>=1.23.2; python_version=='3.11'",
|
| 32 |
+
"numpy>=1.26.0; python_version>='3.12'",
|
| 33 |
+
"python-dateutil>=2.8.2",
|
| 34 |
+
"pytz>=2020.1",
|
| 35 |
+
"tzdata>=2022.7"
|
| 36 |
+
]
|
| 37 |
+
classifiers = [
|
| 38 |
+
'Development Status :: 5 - Production/Stable',
|
| 39 |
+
'Environment :: Console',
|
| 40 |
+
'Intended Audience :: Science/Research',
|
| 41 |
+
'License :: OSI Approved :: BSD License',
|
| 42 |
+
'Operating System :: OS Independent',
|
| 43 |
+
'Programming Language :: Cython',
|
| 44 |
+
'Programming Language :: Python',
|
| 45 |
+
'Programming Language :: Python :: 3',
|
| 46 |
+
'Programming Language :: Python :: 3 :: Only',
|
| 47 |
+
'Programming Language :: Python :: 3.9',
|
| 48 |
+
'Programming Language :: Python :: 3.10',
|
| 49 |
+
'Programming Language :: Python :: 3.11',
|
| 50 |
+
'Programming Language :: Python :: 3.12',
|
| 51 |
+
'Topic :: Scientific/Engineering'
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
[project.urls]
|
| 55 |
+
homepage = 'https://pandas.pydata.org'
|
| 56 |
+
documentation = 'https://pandas.pydata.org/docs/'
|
| 57 |
+
repository = 'https://github.com/pandas-dev/pandas'
|
| 58 |
+
|
| 59 |
+
[project.entry-points."pandas_plotting_backends"]
|
| 60 |
+
matplotlib = "pandas:plotting._matplotlib"
|
| 61 |
+
|
| 62 |
+
[project.optional-dependencies]
|
| 63 |
+
test = ['hypothesis>=6.46.1', 'pytest>=7.3.2', 'pytest-xdist>=2.2.0']
|
| 64 |
+
pyarrow = ['pyarrow>=10.0.1']
|
| 65 |
+
performance = ['bottleneck>=1.3.6', 'numba>=0.56.4', 'numexpr>=2.8.4']
|
| 66 |
+
computation = ['scipy>=1.10.0', 'xarray>=2022.12.0']
|
| 67 |
+
fss = ['fsspec>=2022.11.0']
|
| 68 |
+
aws = ['s3fs>=2022.11.0']
|
| 69 |
+
gcp = ['gcsfs>=2022.11.0', 'pandas-gbq>=0.19.0']
|
| 70 |
+
excel = ['odfpy>=1.4.1', 'openpyxl>=3.1.0', 'python-calamine>=0.1.7', 'pyxlsb>=1.0.10', 'xlrd>=2.0.1', 'xlsxwriter>=3.0.5']
|
| 71 |
+
parquet = ['pyarrow>=10.0.1']
|
| 72 |
+
feather = ['pyarrow>=10.0.1']
|
| 73 |
+
hdf5 = [# blosc only available on conda (https://github.com/Blosc/python-blosc/issues/297)
|
| 74 |
+
#'blosc>=1.20.1',
|
| 75 |
+
'tables>=3.8.0']
|
| 76 |
+
spss = ['pyreadstat>=1.2.0']
|
| 77 |
+
postgresql = ['SQLAlchemy>=2.0.0', 'psycopg2>=2.9.6', 'adbc-driver-postgresql>=0.8.0']
|
| 78 |
+
mysql = ['SQLAlchemy>=2.0.0', 'pymysql>=1.0.2']
|
| 79 |
+
sql-other = ['SQLAlchemy>=2.0.0', 'adbc-driver-postgresql>=0.8.0', 'adbc-driver-sqlite>=0.8.0']
|
| 80 |
+
html = ['beautifulsoup4>=4.11.2', 'html5lib>=1.1', 'lxml>=4.9.2']
|
| 81 |
+
xml = ['lxml>=4.9.2']
|
| 82 |
+
plot = ['matplotlib>=3.6.3']
|
| 83 |
+
output-formatting = ['jinja2>=3.1.2', 'tabulate>=0.9.0']
|
| 84 |
+
clipboard = ['PyQt5>=5.15.9', 'qtpy>=2.3.0']
|
| 85 |
+
compression = ['zstandard>=0.19.0']
|
| 86 |
+
consortium-standard = ['dataframe-api-compat>=0.1.7']
|
| 87 |
+
all = ['adbc-driver-postgresql>=0.8.0',
|
| 88 |
+
'adbc-driver-sqlite>=0.8.0',
|
| 89 |
+
'beautifulsoup4>=4.11.2',
|
| 90 |
+
# blosc only available on conda (https://github.com/Blosc/python-blosc/issues/297)
|
| 91 |
+
#'blosc>=1.21.3',
|
| 92 |
+
'bottleneck>=1.3.6',
|
| 93 |
+
'dataframe-api-compat>=0.1.7',
|
| 94 |
+
'fastparquet>=2022.12.0',
|
| 95 |
+
'fsspec>=2022.11.0',
|
| 96 |
+
'gcsfs>=2022.11.0',
|
| 97 |
+
'html5lib>=1.1',
|
| 98 |
+
'hypothesis>=6.46.1',
|
| 99 |
+
'jinja2>=3.1.2',
|
| 100 |
+
'lxml>=4.9.2',
|
| 101 |
+
'matplotlib>=3.6.3',
|
| 102 |
+
'numba>=0.56.4',
|
| 103 |
+
'numexpr>=2.8.4',
|
| 104 |
+
'odfpy>=1.4.1',
|
| 105 |
+
'openpyxl>=3.1.0',
|
| 106 |
+
'pandas-gbq>=0.19.0',
|
| 107 |
+
'psycopg2>=2.9.6',
|
| 108 |
+
'pyarrow>=10.0.1',
|
| 109 |
+
'pymysql>=1.0.2',
|
| 110 |
+
'PyQt5>=5.15.9',
|
| 111 |
+
'pyreadstat>=1.2.0',
|
| 112 |
+
'pytest>=7.3.2',
|
| 113 |
+
'pytest-xdist>=2.2.0',
|
| 114 |
+
'python-calamine>=0.1.7',
|
| 115 |
+
'pyxlsb>=1.0.10',
|
| 116 |
+
'qtpy>=2.3.0',
|
| 117 |
+
'scipy>=1.10.0',
|
| 118 |
+
's3fs>=2022.11.0',
|
| 119 |
+
'SQLAlchemy>=2.0.0',
|
| 120 |
+
'tables>=3.8.0',
|
| 121 |
+
'tabulate>=0.9.0',
|
| 122 |
+
'xarray>=2022.12.0',
|
| 123 |
+
'xlrd>=2.0.1',
|
| 124 |
+
'xlsxwriter>=3.0.5',
|
| 125 |
+
'zstandard>=0.19.0']
|
| 126 |
+
|
| 127 |
+
# TODO: Remove after setuptools support is dropped.
|
| 128 |
+
[tool.setuptools]
|
| 129 |
+
include-package-data = true
|
| 130 |
+
|
| 131 |
+
[tool.setuptools.packages.find]
|
| 132 |
+
include = ["pandas", "pandas.*"]
|
| 133 |
+
namespaces = false
|
| 134 |
+
|
| 135 |
+
[tool.setuptools.exclude-package-data]
|
| 136 |
+
"*" = ["*.c", "*.h"]
|
| 137 |
+
|
| 138 |
+
# See the docstring in versioneer.py for instructions. Note that you must
|
| 139 |
+
# re-run 'versioneer.py setup' after changing this section, and commit the
|
| 140 |
+
# resulting files.
|
| 141 |
+
[tool.versioneer]
|
| 142 |
+
VCS = "git"
|
| 143 |
+
style = "pep440"
|
| 144 |
+
versionfile_source = "pandas/_version.py"
|
| 145 |
+
versionfile_build = "pandas/_version.py"
|
| 146 |
+
tag_prefix = "v"
|
| 147 |
+
parentdir_prefix = "pandas-"
|
| 148 |
+
|
| 149 |
+
[tool.meson-python.args]
|
| 150 |
+
setup = ['--vsenv'] # For Windows
|
| 151 |
+
|
| 152 |
+
[tool.cibuildwheel]
|
| 153 |
+
skip = "cp36-* cp37-* cp38-* pp* *_i686 *_ppc64le *_s390x"
|
| 154 |
+
build-verbosity = "3"
|
| 155 |
+
environment = {LDFLAGS="-Wl,--strip-all"}
|
| 156 |
+
# pytz 2024.2 causing some failures
|
| 157 |
+
test-requires = "hypothesis>=6.46.1 pytest>=7.3.2 pytest-xdist>=2.2.0 pytz<2024.2"
|
| 158 |
+
test-command = """
|
| 159 |
+
PANDAS_CI='1' python -c 'import pandas as pd; \
|
| 160 |
+
pd.test(extra_args=["-m not clipboard and not single_cpu and not slow and not network and not db", "-n 2", "--no-strict-data-files"]); \
|
| 161 |
+
pd.test(extra_args=["-m not clipboard and single_cpu and not slow and not network and not db", "--no-strict-data-files"]);' \
|
| 162 |
+
"""
|
| 163 |
+
free-threaded-support = true
|
| 164 |
+
before-build = "PACKAGE_DIR={package} bash {package}/scripts/cibw_before_build.sh"
|
| 165 |
+
|
| 166 |
+
[tool.cibuildwheel.windows]
|
| 167 |
+
before-build = "pip install delvewheel && bash {package}/scripts/cibw_before_build.sh"
|
| 168 |
+
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"
|
| 169 |
+
|
| 170 |
+
[[tool.cibuildwheel.overrides]]
|
| 171 |
+
select = "*-manylinux_aarch64*"
|
| 172 |
+
test-command = """
|
| 173 |
+
PANDAS_CI='1' python -c 'import pandas as pd; \
|
| 174 |
+
pd.test(extra_args=["-m not clipboard and not single_cpu and not slow and not network and not db and not fails_arm_wheels", "-n 2", "--no-strict-data-files"]); \
|
| 175 |
+
pd.test(extra_args=["-m not clipboard and single_cpu and not slow and not network and not db", "--no-strict-data-files"]);' \
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
[[tool.cibuildwheel.overrides]]
|
| 179 |
+
select = "*-musllinux*"
|
| 180 |
+
before-test = "apk update && apk add musl-locales"
|
| 181 |
+
|
| 182 |
+
[[tool.cibuildwheel.overrides]]
|
| 183 |
+
select = "*-win*"
|
| 184 |
+
# We test separately for Windows, since we use
|
| 185 |
+
# the windowsservercore docker image to check if any dlls are
|
| 186 |
+
# missing from the wheel
|
| 187 |
+
test-command = ""
|
| 188 |
+
|
| 189 |
+
[[tool.cibuildwheel.overrides]]
|
| 190 |
+
# Don't strip wheels on macOS.
|
| 191 |
+
# macOS doesn't support stripping wheels with linker
|
| 192 |
+
# https://github.com/MacPython/numpy-wheels/pull/87#issuecomment-624878264
|
| 193 |
+
select = "*-macosx*"
|
| 194 |
+
environment = {CFLAGS="-g0"}
|
| 195 |
+
|
| 196 |
+
[tool.black]
|
| 197 |
+
target-version = ['py39', 'py310']
|
| 198 |
+
required-version = '23.11.0'
|
| 199 |
+
exclude = '''
|
| 200 |
+
(
|
| 201 |
+
asv_bench/env
|
| 202 |
+
| \.egg
|
| 203 |
+
| \.git
|
| 204 |
+
| \.hg
|
| 205 |
+
| \.mypy_cache
|
| 206 |
+
| \.nox
|
| 207 |
+
| \.tox
|
| 208 |
+
| \.venv
|
| 209 |
+
| _build
|
| 210 |
+
| buck-out
|
| 211 |
+
| build
|
| 212 |
+
| dist
|
| 213 |
+
| setup.py
|
| 214 |
+
)
|
| 215 |
+
'''
|
| 216 |
+
|
| 217 |
+
[tool.ruff]
|
| 218 |
+
line-length = 88
|
| 219 |
+
target-version = "py310"
|
| 220 |
+
fix = true
|
| 221 |
+
unfixable = []
|
| 222 |
+
typing-modules = ["pandas._typing"]
|
| 223 |
+
|
| 224 |
+
select = [
|
| 225 |
+
# pyflakes
|
| 226 |
+
"F",
|
| 227 |
+
# pycodestyle
|
| 228 |
+
"E", "W",
|
| 229 |
+
# flake8-2020
|
| 230 |
+
"YTT",
|
| 231 |
+
# flake8-bugbear
|
| 232 |
+
"B",
|
| 233 |
+
# flake8-quotes
|
| 234 |
+
"Q",
|
| 235 |
+
# flake8-debugger
|
| 236 |
+
"T10",
|
| 237 |
+
# flake8-gettext
|
| 238 |
+
"INT",
|
| 239 |
+
# pylint
|
| 240 |
+
"PL",
|
| 241 |
+
# misc lints
|
| 242 |
+
"PIE",
|
| 243 |
+
# flake8-pyi
|
| 244 |
+
"PYI",
|
| 245 |
+
# tidy imports
|
| 246 |
+
"TID",
|
| 247 |
+
# implicit string concatenation
|
| 248 |
+
"ISC",
|
| 249 |
+
# type-checking imports
|
| 250 |
+
"TCH",
|
| 251 |
+
# comprehensions
|
| 252 |
+
"C4",
|
| 253 |
+
# pygrep-hooks
|
| 254 |
+
"PGH",
|
| 255 |
+
# Ruff-specific rules
|
| 256 |
+
"RUF",
|
| 257 |
+
# flake8-bandit: exec-builtin
|
| 258 |
+
"S102",
|
| 259 |
+
# numpy-legacy-random
|
| 260 |
+
"NPY002",
|
| 261 |
+
# Perflint
|
| 262 |
+
"PERF",
|
| 263 |
+
# flynt
|
| 264 |
+
"FLY",
|
| 265 |
+
# flake8-logging-format
|
| 266 |
+
"G",
|
| 267 |
+
# flake8-future-annotations
|
| 268 |
+
"FA",
|
| 269 |
+
]
|
| 270 |
+
|
| 271 |
+
ignore = [
|
| 272 |
+
### Intentionally disabled
|
| 273 |
+
# space before : (needed for how black formats slicing)
|
| 274 |
+
"E203",
|
| 275 |
+
# module level import not at top of file
|
| 276 |
+
"E402",
|
| 277 |
+
# do not assign a lambda expression, use a def
|
| 278 |
+
"E731",
|
| 279 |
+
# line break before binary operator
|
| 280 |
+
# "W503", # not yet implemented
|
| 281 |
+
# line break after binary operator
|
| 282 |
+
# "W504", # not yet implemented
|
| 283 |
+
# controversial
|
| 284 |
+
"B006",
|
| 285 |
+
# controversial
|
| 286 |
+
"B007",
|
| 287 |
+
# controversial
|
| 288 |
+
"B008",
|
| 289 |
+
# setattr is used to side-step mypy
|
| 290 |
+
"B009",
|
| 291 |
+
# getattr is used to side-step mypy
|
| 292 |
+
"B010",
|
| 293 |
+
# tests use assert False
|
| 294 |
+
"B011",
|
| 295 |
+
# tests use comparisons but not their returned value
|
| 296 |
+
"B015",
|
| 297 |
+
# false positives
|
| 298 |
+
"B019",
|
| 299 |
+
# Loop control variable overrides iterable it iterates
|
| 300 |
+
"B020",
|
| 301 |
+
# Function definition does not bind loop variable
|
| 302 |
+
"B023",
|
| 303 |
+
# Functions defined inside a loop must not use variables redefined in the loop
|
| 304 |
+
# "B301", # not yet implemented
|
| 305 |
+
# Only works with python >=3.10
|
| 306 |
+
"B905",
|
| 307 |
+
# Too many arguments to function call
|
| 308 |
+
"PLR0913",
|
| 309 |
+
# Too many returns
|
| 310 |
+
"PLR0911",
|
| 311 |
+
# Too many branches
|
| 312 |
+
"PLR0912",
|
| 313 |
+
# Too many statements
|
| 314 |
+
"PLR0915",
|
| 315 |
+
# Redefined loop name
|
| 316 |
+
"PLW2901",
|
| 317 |
+
# Global statements are discouraged
|
| 318 |
+
"PLW0603",
|
| 319 |
+
# Docstrings should not be included in stubs
|
| 320 |
+
"PYI021",
|
| 321 |
+
# Use `typing.NamedTuple` instead of `collections.namedtuple`
|
| 322 |
+
"PYI024",
|
| 323 |
+
# No builtin `eval()` allowed
|
| 324 |
+
"PGH001",
|
| 325 |
+
# compare-to-empty-string
|
| 326 |
+
"PLC1901",
|
| 327 |
+
# while int | float can be shortened to float, the former is more explicit
|
| 328 |
+
"PYI041",
|
| 329 |
+
# incorrect-dict-iterator, flags valid Series.items usage
|
| 330 |
+
"PERF102",
|
| 331 |
+
# try-except-in-loop, becomes useless in Python 3.11
|
| 332 |
+
"PERF203",
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
### TODO: Enable gradually
|
| 336 |
+
# Useless statement
|
| 337 |
+
"B018",
|
| 338 |
+
# Within an except clause, raise exceptions with ...
|
| 339 |
+
"B904",
|
| 340 |
+
# Magic number
|
| 341 |
+
"PLR2004",
|
| 342 |
+
# comparison-with-itself
|
| 343 |
+
"PLR0124",
|
| 344 |
+
# Consider `elif` instead of `else` then `if` to remove indentation level
|
| 345 |
+
"PLR5501",
|
| 346 |
+
# collection-literal-concatenation
|
| 347 |
+
"RUF005",
|
| 348 |
+
# pairwise-over-zipped (>=PY310 only)
|
| 349 |
+
"RUF007",
|
| 350 |
+
# explicit-f-string-type-conversion
|
| 351 |
+
"RUF010",
|
| 352 |
+
# mutable-class-default
|
| 353 |
+
"RUF012"
|
| 354 |
+
]
|
| 355 |
+
|
| 356 |
+
exclude = [
|
| 357 |
+
"doc/sphinxext/*.py",
|
| 358 |
+
"doc/build/*.py",
|
| 359 |
+
"doc/temp/*.py",
|
| 360 |
+
".eggs/*.py",
|
| 361 |
+
# vendored files
|
| 362 |
+
"pandas/util/version/*",
|
| 363 |
+
"pandas/io/clipboard/__init__.py",
|
| 364 |
+
# exclude asv benchmark environments from linting
|
| 365 |
+
"env",
|
| 366 |
+
]
|
| 367 |
+
|
| 368 |
+
[tool.ruff.per-file-ignores]
|
| 369 |
+
# relative imports allowed for asv_bench
|
| 370 |
+
"asv_bench/*" = ["TID", "NPY002"]
|
| 371 |
+
# to be enabled gradually
|
| 372 |
+
"pandas/core/*" = ["PLR5501"]
|
| 373 |
+
"pandas/tests/*" = ["B028", "FLY"]
|
| 374 |
+
"scripts/*" = ["B028"]
|
| 375 |
+
# Keep this one enabled
|
| 376 |
+
"pandas/_typing.py" = ["TCH"]
|
| 377 |
+
|
| 378 |
+
[tool.pylint.messages_control]
|
| 379 |
+
max-line-length = 88
|
| 380 |
+
disable = [
|
| 381 |
+
# intentionally turned off
|
| 382 |
+
"bad-mcs-classmethod-argument",
|
| 383 |
+
"broad-except",
|
| 384 |
+
"c-extension-no-member",
|
| 385 |
+
"comparison-with-itself",
|
| 386 |
+
"consider-using-enumerate",
|
| 387 |
+
"import-error",
|
| 388 |
+
"import-outside-toplevel",
|
| 389 |
+
"invalid-name",
|
| 390 |
+
"invalid-unary-operand-type",
|
| 391 |
+
"line-too-long",
|
| 392 |
+
"no-else-continue",
|
| 393 |
+
"no-else-raise",
|
| 394 |
+
"no-else-return",
|
| 395 |
+
"no-member",
|
| 396 |
+
"no-name-in-module",
|
| 397 |
+
"not-an-iterable",
|
| 398 |
+
"overridden-final-method",
|
| 399 |
+
"pointless-statement",
|
| 400 |
+
"redundant-keyword-arg",
|
| 401 |
+
"singleton-comparison",
|
| 402 |
+
"too-many-ancestors",
|
| 403 |
+
"too-many-arguments",
|
| 404 |
+
"too-many-boolean-expressions",
|
| 405 |
+
"too-many-branches",
|
| 406 |
+
"too-many-function-args",
|
| 407 |
+
"too-many-instance-attributes",
|
| 408 |
+
"too-many-locals",
|
| 409 |
+
"too-many-nested-blocks",
|
| 410 |
+
"too-many-public-methods",
|
| 411 |
+
"too-many-return-statements",
|
| 412 |
+
"too-many-statements",
|
| 413 |
+
"unexpected-keyword-arg",
|
| 414 |
+
"ungrouped-imports",
|
| 415 |
+
"unsubscriptable-object",
|
| 416 |
+
"unsupported-assignment-operation",
|
| 417 |
+
"unsupported-membership-test",
|
| 418 |
+
"unused-import",
|
| 419 |
+
"use-dict-literal",
|
| 420 |
+
"use-implicit-booleaness-not-comparison",
|
| 421 |
+
"use-implicit-booleaness-not-len",
|
| 422 |
+
"wrong-import-order",
|
| 423 |
+
"wrong-import-position",
|
| 424 |
+
"redefined-loop-name",
|
| 425 |
+
|
| 426 |
+
# misc
|
| 427 |
+
"abstract-class-instantiated",
|
| 428 |
+
"no-value-for-parameter",
|
| 429 |
+
"undefined-variable",
|
| 430 |
+
"unpacking-non-sequence",
|
| 431 |
+
"used-before-assignment",
|
| 432 |
+
|
| 433 |
+
# pylint type "C": convention, for programming standard violation
|
| 434 |
+
"missing-class-docstring",
|
| 435 |
+
"missing-function-docstring",
|
| 436 |
+
"missing-module-docstring",
|
| 437 |
+
"superfluous-parens",
|
| 438 |
+
"too-many-lines",
|
| 439 |
+
"unidiomatic-typecheck",
|
| 440 |
+
"unnecessary-dunder-call",
|
| 441 |
+
"unnecessary-lambda-assignment",
|
| 442 |
+
|
| 443 |
+
# pylint type "R": refactor, for bad code smell
|
| 444 |
+
"consider-using-with",
|
| 445 |
+
"cyclic-import",
|
| 446 |
+
"duplicate-code",
|
| 447 |
+
"inconsistent-return-statements",
|
| 448 |
+
"redefined-argument-from-local",
|
| 449 |
+
"too-few-public-methods",
|
| 450 |
+
|
| 451 |
+
# pylint type "W": warning, for python specific problems
|
| 452 |
+
"abstract-method",
|
| 453 |
+
"arguments-differ",
|
| 454 |
+
"arguments-out-of-order",
|
| 455 |
+
"arguments-renamed",
|
| 456 |
+
"attribute-defined-outside-init",
|
| 457 |
+
"broad-exception-raised",
|
| 458 |
+
"comparison-with-callable",
|
| 459 |
+
"dangerous-default-value",
|
| 460 |
+
"deprecated-module",
|
| 461 |
+
"eval-used",
|
| 462 |
+
"expression-not-assigned",
|
| 463 |
+
"fixme",
|
| 464 |
+
"global-statement",
|
| 465 |
+
"invalid-overridden-method",
|
| 466 |
+
"keyword-arg-before-vararg",
|
| 467 |
+
"possibly-unused-variable",
|
| 468 |
+
"protected-access",
|
| 469 |
+
"raise-missing-from",
|
| 470 |
+
"redefined-builtin",
|
| 471 |
+
"redefined-outer-name",
|
| 472 |
+
"self-cls-assignment",
|
| 473 |
+
"signature-differs",
|
| 474 |
+
"super-init-not-called",
|
| 475 |
+
"try-except-raise",
|
| 476 |
+
"unnecessary-lambda",
|
| 477 |
+
"unused-argument",
|
| 478 |
+
"unused-variable",
|
| 479 |
+
"using-constant-test"
|
| 480 |
+
]
|
| 481 |
+
|
| 482 |
+
[tool.pytest.ini_options]
|
| 483 |
+
# sync minversion with pyproject.toml & install.rst
|
| 484 |
+
minversion = "7.3.2"
|
| 485 |
+
addopts = "--strict-markers --strict-config --capture=no --durations=30 --junitxml=test-data.xml"
|
| 486 |
+
empty_parameter_set_mark = "fail_at_collect"
|
| 487 |
+
xfail_strict = true
|
| 488 |
+
testpaths = "pandas"
|
| 489 |
+
doctest_optionflags = [
|
| 490 |
+
"NORMALIZE_WHITESPACE",
|
| 491 |
+
"IGNORE_EXCEPTION_DETAIL",
|
| 492 |
+
"ELLIPSIS",
|
| 493 |
+
]
|
| 494 |
+
filterwarnings = [
|
| 495 |
+
"error:::pandas",
|
| 496 |
+
"error::ResourceWarning",
|
| 497 |
+
"error::pytest.PytestUnraisableExceptionWarning",
|
| 498 |
+
# TODO(PY311-minimum): Specify EncodingWarning
|
| 499 |
+
# Ignore 3rd party EncodingWarning but raise on pandas'
|
| 500 |
+
"ignore:.*encoding.* argument not specified",
|
| 501 |
+
"error:.*encoding.* argument not specified::pandas",
|
| 502 |
+
"ignore:.*ssl.SSLSocket:pytest.PytestUnraisableExceptionWarning",
|
| 503 |
+
"ignore:.*ssl.SSLSocket:ResourceWarning",
|
| 504 |
+
# GH 44844: Can remove once minimum matplotlib version >= 3.7
|
| 505 |
+
"ignore:.*FileIO:pytest.PytestUnraisableExceptionWarning",
|
| 506 |
+
"ignore:.*BufferedRandom:ResourceWarning",
|
| 507 |
+
"ignore::ResourceWarning:asyncio",
|
| 508 |
+
# From plotting doctests
|
| 509 |
+
"ignore:More than 20 figures have been opened:RuntimeWarning",
|
| 510 |
+
# Will be fixed in numba 0.56: https://github.com/numba/numba/issues/7758
|
| 511 |
+
"ignore:`np.MachAr` is deprecated:DeprecationWarning:numba",
|
| 512 |
+
"ignore:.*urllib3:DeprecationWarning:botocore",
|
| 513 |
+
"ignore:Setuptools is replacing distutils.:UserWarning:_distutils_hack",
|
| 514 |
+
# https://github.com/PyTables/PyTables/issues/822
|
| 515 |
+
"ignore:a closed node found in the registry:UserWarning:tables",
|
| 516 |
+
"ignore:`np.object` is a deprecated:DeprecationWarning:tables",
|
| 517 |
+
"ignore:tostring:DeprecationWarning:tables",
|
| 518 |
+
"ignore:distutils Version classes are deprecated:DeprecationWarning:pandas_datareader",
|
| 519 |
+
"ignore:distutils Version classes are deprecated:DeprecationWarning:numexpr",
|
| 520 |
+
"ignore:distutils Version classes are deprecated:DeprecationWarning:fastparquet",
|
| 521 |
+
"ignore:distutils Version classes are deprecated:DeprecationWarning:fsspec",
|
| 522 |
+
# Can be removed once https://github.com/numpy/numpy/pull/24794 is merged
|
| 523 |
+
"ignore:.*In the future `np.long` will be defined as.*:FutureWarning",
|
| 524 |
+
]
|
| 525 |
+
junit_family = "xunit2"
|
| 526 |
+
markers = [
|
| 527 |
+
"single_cpu: tests that should run on a single cpu only",
|
| 528 |
+
"slow: mark a test as slow",
|
| 529 |
+
"network: mark a test as network",
|
| 530 |
+
"db: tests requiring a database (mysql or postgres)",
|
| 531 |
+
"clipboard: mark a pd.read_clipboard test",
|
| 532 |
+
"arm_slow: mark a test as slow for arm64 architecture",
|
| 533 |
+
"skip_ubsan: Tests known to fail UBSAN check",
|
| 534 |
+
# TODO: someone should investigate this ...
|
| 535 |
+
# these tests only fail in the wheel builder and don't fail in regular
|
| 536 |
+
# ARM CI
|
| 537 |
+
"fails_arm_wheels: Tests that fail in the ARM wheel build only",
|
| 538 |
+
]
|
| 539 |
+
|
| 540 |
+
[tool.mypy]
|
| 541 |
+
# Import discovery
|
| 542 |
+
mypy_path = "typings"
|
| 543 |
+
files = ["pandas", "typings"]
|
| 544 |
+
namespace_packages = false
|
| 545 |
+
explicit_package_bases = false
|
| 546 |
+
ignore_missing_imports = true
|
| 547 |
+
follow_imports = "normal"
|
| 548 |
+
follow_imports_for_stubs = false
|
| 549 |
+
no_site_packages = false
|
| 550 |
+
no_silence_site_packages = false
|
| 551 |
+
# Platform configuration
|
| 552 |
+
python_version = "3.11"
|
| 553 |
+
platform = "linux-64"
|
| 554 |
+
# Disallow dynamic typing
|
| 555 |
+
disallow_any_unimported = false # TODO
|
| 556 |
+
disallow_any_expr = false # TODO
|
| 557 |
+
disallow_any_decorated = false # TODO
|
| 558 |
+
disallow_any_explicit = false # TODO
|
| 559 |
+
disallow_any_generics = false # TODO
|
| 560 |
+
disallow_subclassing_any = false # TODO
|
| 561 |
+
# Untyped definitions and calls
|
| 562 |
+
disallow_untyped_calls = true
|
| 563 |
+
disallow_untyped_defs = true
|
| 564 |
+
disallow_incomplete_defs = true
|
| 565 |
+
check_untyped_defs = true
|
| 566 |
+
disallow_untyped_decorators = true
|
| 567 |
+
# None and Optional handling
|
| 568 |
+
no_implicit_optional = true
|
| 569 |
+
strict_optional = true
|
| 570 |
+
# Configuring warnings
|
| 571 |
+
warn_redundant_casts = true
|
| 572 |
+
warn_unused_ignores = true
|
| 573 |
+
warn_no_return = true
|
| 574 |
+
warn_return_any = false # TODO
|
| 575 |
+
warn_unreachable = false # GH#27396
|
| 576 |
+
# Suppressing errors
|
| 577 |
+
ignore_errors = false
|
| 578 |
+
enable_error_code = "ignore-without-code"
|
| 579 |
+
# Miscellaneous strictness flags
|
| 580 |
+
allow_untyped_globals = false
|
| 581 |
+
allow_redefinition = false
|
| 582 |
+
local_partial_types = false
|
| 583 |
+
implicit_reexport = true
|
| 584 |
+
strict_equality = true
|
| 585 |
+
# Configuring error messages
|
| 586 |
+
show_error_context = false
|
| 587 |
+
show_column_numbers = false
|
| 588 |
+
show_error_codes = true
|
| 589 |
+
|
| 590 |
+
[[tool.mypy.overrides]]
|
| 591 |
+
module = [
|
| 592 |
+
"pandas._config.config", # TODO
|
| 593 |
+
"pandas._libs.*",
|
| 594 |
+
"pandas._testing.*", # TODO
|
| 595 |
+
"pandas.arrays", # TODO
|
| 596 |
+
"pandas.compat.numpy.function", # TODO
|
| 597 |
+
"pandas.compat._optional", # TODO
|
| 598 |
+
"pandas.compat.compressors", # TODO
|
| 599 |
+
"pandas.compat.pickle_compat", # TODO
|
| 600 |
+
"pandas.core._numba.executor", # TODO
|
| 601 |
+
"pandas.core.array_algos.datetimelike_accumulations", # TODO
|
| 602 |
+
"pandas.core.array_algos.masked_accumulations", # TODO
|
| 603 |
+
"pandas.core.array_algos.masked_reductions", # TODO
|
| 604 |
+
"pandas.core.array_algos.putmask", # TODO
|
| 605 |
+
"pandas.core.array_algos.quantile", # TODO
|
| 606 |
+
"pandas.core.array_algos.replace", # TODO
|
| 607 |
+
"pandas.core.array_algos.take", # TODO
|
| 608 |
+
"pandas.core.arrays.*", # TODO
|
| 609 |
+
"pandas.core.computation.*", # TODO
|
| 610 |
+
"pandas.core.dtypes.astype", # TODO
|
| 611 |
+
"pandas.core.dtypes.cast", # TODO
|
| 612 |
+
"pandas.core.dtypes.common", # TODO
|
| 613 |
+
"pandas.core.dtypes.concat", # TODO
|
| 614 |
+
"pandas.core.dtypes.dtypes", # TODO
|
| 615 |
+
"pandas.core.dtypes.generic", # TODO
|
| 616 |
+
"pandas.core.dtypes.inference", # TODO
|
| 617 |
+
"pandas.core.dtypes.missing", # TODO
|
| 618 |
+
"pandas.core.groupby.categorical", # TODO
|
| 619 |
+
"pandas.core.groupby.generic", # TODO
|
| 620 |
+
"pandas.core.groupby.grouper", # TODO
|
| 621 |
+
"pandas.core.groupby.groupby", # TODO
|
| 622 |
+
"pandas.core.groupby.ops", # TODO
|
| 623 |
+
"pandas.core.indexers.*", # TODO
|
| 624 |
+
"pandas.core.indexes.*", # TODO
|
| 625 |
+
"pandas.core.interchange.column", # TODO
|
| 626 |
+
"pandas.core.interchange.dataframe_protocol", # TODO
|
| 627 |
+
"pandas.core.interchange.from_dataframe", # TODO
|
| 628 |
+
"pandas.core.internals.*", # TODO
|
| 629 |
+
"pandas.core.methods.*", # TODO
|
| 630 |
+
"pandas.core.ops.array_ops", # TODO
|
| 631 |
+
"pandas.core.ops.common", # TODO
|
| 632 |
+
"pandas.core.ops.invalid", # TODO
|
| 633 |
+
"pandas.core.ops.mask_ops", # TODO
|
| 634 |
+
"pandas.core.ops.missing", # TODO
|
| 635 |
+
"pandas.core.reshape.*", # TODO
|
| 636 |
+
"pandas.core.strings.*", # TODO
|
| 637 |
+
"pandas.core.tools.*", # TODO
|
| 638 |
+
"pandas.core.window.common", # TODO
|
| 639 |
+
"pandas.core.window.ewm", # TODO
|
| 640 |
+
"pandas.core.window.expanding", # TODO
|
| 641 |
+
"pandas.core.window.numba_", # TODO
|
| 642 |
+
"pandas.core.window.online", # TODO
|
| 643 |
+
"pandas.core.window.rolling", # TODO
|
| 644 |
+
"pandas.core.accessor", # TODO
|
| 645 |
+
"pandas.core.algorithms", # TODO
|
| 646 |
+
"pandas.core.apply", # TODO
|
| 647 |
+
"pandas.core.arraylike", # TODO
|
| 648 |
+
"pandas.core.base", # TODO
|
| 649 |
+
"pandas.core.common", # TODO
|
| 650 |
+
"pandas.core.config_init", # TODO
|
| 651 |
+
"pandas.core.construction", # TODO
|
| 652 |
+
"pandas.core.flags", # TODO
|
| 653 |
+
"pandas.core.frame", # TODO
|
| 654 |
+
"pandas.core.generic", # TODO
|
| 655 |
+
"pandas.core.indexing", # TODO
|
| 656 |
+
"pandas.core.missing", # TODO
|
| 657 |
+
"pandas.core.nanops", # TODO
|
| 658 |
+
"pandas.core.resample", # TODO
|
| 659 |
+
"pandas.core.roperator", # TODO
|
| 660 |
+
"pandas.core.sample", # TODO
|
| 661 |
+
"pandas.core.series", # TODO
|
| 662 |
+
"pandas.core.sorting", # TODO
|
| 663 |
+
"pandas.errors", # TODO
|
| 664 |
+
"pandas.io.clipboard", # TODO
|
| 665 |
+
"pandas.io.excel._base", # TODO
|
| 666 |
+
"pandas.io.excel._odfreader", # TODO
|
| 667 |
+
"pandas.io.excel._odswriter", # TODO
|
| 668 |
+
"pandas.io.excel._openpyxl", # TODO
|
| 669 |
+
"pandas.io.excel._pyxlsb", # TODO
|
| 670 |
+
"pandas.io.excel._xlrd", # TODO
|
| 671 |
+
"pandas.io.excel._xlsxwriter", # TODO
|
| 672 |
+
"pandas.io.formats.console", # TODO
|
| 673 |
+
"pandas.io.formats.css", # TODO
|
| 674 |
+
"pandas.io.formats.excel", # TODO
|
| 675 |
+
"pandas.io.formats.format", # TODO
|
| 676 |
+
"pandas.io.formats.info", # TODO
|
| 677 |
+
"pandas.io.formats.printing", # TODO
|
| 678 |
+
"pandas.io.formats.style", # TODO
|
| 679 |
+
"pandas.io.formats.style_render", # TODO
|
| 680 |
+
"pandas.io.formats.xml", # TODO
|
| 681 |
+
"pandas.io.json.*", # TODO
|
| 682 |
+
"pandas.io.parsers.*", # TODO
|
| 683 |
+
"pandas.io.sas.sas_xport", # TODO
|
| 684 |
+
"pandas.io.sas.sas7bdat", # TODO
|
| 685 |
+
"pandas.io.clipboards", # TODO
|
| 686 |
+
"pandas.io.common", # TODO
|
| 687 |
+
"pandas.io.gbq", # TODO
|
| 688 |
+
"pandas.io.html", # TODO
|
| 689 |
+
"pandas.io.gbq", # TODO
|
| 690 |
+
"pandas.io.parquet", # TODO
|
| 691 |
+
"pandas.io.pytables", # TODO
|
| 692 |
+
"pandas.io.sql", # TODO
|
| 693 |
+
"pandas.io.stata", # TODO
|
| 694 |
+
"pandas.io.xml", # TODO
|
| 695 |
+
"pandas.plotting.*", # TODO
|
| 696 |
+
"pandas.tests.*",
|
| 697 |
+
"pandas.tseries.frequencies", # TODO
|
| 698 |
+
"pandas.tseries.holiday", # TODO
|
| 699 |
+
"pandas.util._decorators", # TODO
|
| 700 |
+
"pandas.util._doctools", # TODO
|
| 701 |
+
"pandas.util._print_versions", # TODO
|
| 702 |
+
"pandas.util._test_decorators", # TODO
|
| 703 |
+
"pandas.util._validators", # TODO
|
| 704 |
+
"pandas.util", # TODO
|
| 705 |
+
"pandas._version",
|
| 706 |
+
"pandas.conftest",
|
| 707 |
+
"pandas"
|
| 708 |
+
]
|
| 709 |
+
disallow_untyped_calls = false
|
| 710 |
+
disallow_untyped_defs = false
|
| 711 |
+
disallow_incomplete_defs = false
|
| 712 |
+
|
| 713 |
+
[[tool.mypy.overrides]]
|
| 714 |
+
module = [
|
| 715 |
+
"pandas.tests.*",
|
| 716 |
+
"pandas._version",
|
| 717 |
+
"pandas.io.clipboard",
|
| 718 |
+
]
|
| 719 |
+
check_untyped_defs = false
|
| 720 |
+
|
| 721 |
+
[[tool.mypy.overrides]]
|
| 722 |
+
module = [
|
| 723 |
+
"pandas.tests.apply.test_series_apply",
|
| 724 |
+
"pandas.tests.arithmetic.conftest",
|
| 725 |
+
"pandas.tests.arrays.sparse.test_combine_concat",
|
| 726 |
+
"pandas.tests.dtypes.test_common",
|
| 727 |
+
"pandas.tests.frame.methods.test_to_records",
|
| 728 |
+
"pandas.tests.groupby.test_rank",
|
| 729 |
+
"pandas.tests.groupby.transform.test_transform",
|
| 730 |
+
"pandas.tests.indexes.interval.test_interval",
|
| 731 |
+
"pandas.tests.indexing.test_categorical",
|
| 732 |
+
"pandas.tests.io.excel.test_writers",
|
| 733 |
+
"pandas.tests.reductions.test_reductions",
|
| 734 |
+
"pandas.tests.test_expressions",
|
| 735 |
+
]
|
| 736 |
+
ignore_errors = true
|
| 737 |
+
|
| 738 |
+
# To be kept consistent with "Import Formatting" section in contributing.rst
|
| 739 |
+
[tool.isort]
|
| 740 |
+
known_pre_libs = "pandas._config"
|
| 741 |
+
known_pre_core = ["pandas._libs", "pandas._typing", "pandas.util._*", "pandas.compat", "pandas.errors"]
|
| 742 |
+
known_dtypes = "pandas.core.dtypes"
|
| 743 |
+
known_post_core = ["pandas.tseries", "pandas.io", "pandas.plotting"]
|
| 744 |
+
sections = ["FUTURE", "STDLIB", "THIRDPARTY" ,"PRE_LIBS" , "PRE_CORE", "DTYPES", "FIRSTPARTY", "POST_CORE", "LOCALFOLDER"]
|
| 745 |
+
profile = "black"
|
| 746 |
+
combine_as_imports = true
|
| 747 |
+
force_grid_wrap = 2
|
| 748 |
+
force_sort_within_sections = true
|
| 749 |
+
skip_glob = "env"
|
| 750 |
+
skip = "pandas/__init__.py"
|
| 751 |
+
|
| 752 |
+
[tool.pyright]
|
| 753 |
+
pythonVersion = "3.11"
|
| 754 |
+
typeCheckingMode = "basic"
|
| 755 |
+
useLibraryCodeForTypes = false
|
| 756 |
+
include = ["pandas", "typings"]
|
| 757 |
+
exclude = ["pandas/tests", "pandas/io/clipboard", "pandas/util/version", "pandas/core/_numba/extensions.py"]
|
| 758 |
+
# enable subset of "strict"
|
| 759 |
+
reportDuplicateImport = true
|
| 760 |
+
reportInconsistentConstructor = true
|
| 761 |
+
reportInvalidStubStatement = true
|
| 762 |
+
reportOverlappingOverload = true
|
| 763 |
+
reportPropertyTypeMismatch = true
|
| 764 |
+
reportUntypedClassDecorator = true
|
| 765 |
+
reportUntypedFunctionDecorator = true
|
| 766 |
+
reportUntypedNamedTuple = true
|
| 767 |
+
reportUnusedImport = true
|
| 768 |
+
disableBytesTypePromotions = true
|
| 769 |
+
# disable subset of "basic"
|
| 770 |
+
reportGeneralTypeIssues = false
|
| 771 |
+
reportMissingModuleSource = false
|
| 772 |
+
reportOptionalCall = false
|
| 773 |
+
reportOptionalIterable = false
|
| 774 |
+
reportOptionalMemberAccess = false
|
| 775 |
+
reportOptionalOperand = false
|
| 776 |
+
reportOptionalSubscript = false
|
| 777 |
+
reportPrivateImportUsage = false
|
| 778 |
+
reportUnboundVariable = false
|
| 779 |
+
|
| 780 |
+
[tool.coverage.run]
|
| 781 |
+
branch = true
|
| 782 |
+
omit = ["pandas/_typing.py", "pandas/_version.py"]
|
| 783 |
+
plugins = ["Cython.Coverage"]
|
| 784 |
+
source = ["pandas"]
|
| 785 |
+
|
| 786 |
+
[tool.coverage.report]
|
| 787 |
+
ignore_errors = false
|
| 788 |
+
show_missing = true
|
| 789 |
+
omit = ["pandas/_version.py"]
|
| 790 |
+
exclude_lines = [
|
| 791 |
+
# Have to re-enable the standard pragma
|
| 792 |
+
"pragma: no cover",
|
| 793 |
+
# Don't complain about missing debug-only code:s
|
| 794 |
+
"def __repr__",
|
| 795 |
+
"if self.debug",
|
| 796 |
+
# Don't complain if tests don't hit defensive assertion code:
|
| 797 |
+
"raise AssertionError",
|
| 798 |
+
"raise NotImplementedError",
|
| 799 |
+
"AbstractMethodError",
|
| 800 |
+
# Don't complain if non-runnable code isn't run:
|
| 801 |
+
"if 0:",
|
| 802 |
+
"if __name__ == .__main__.:",
|
| 803 |
+
"if TYPE_CHECKING:",
|
| 804 |
+
]
|
| 805 |
+
|
| 806 |
+
[tool.coverage.html]
|
| 807 |
+
directory = "coverage_html_report"
|
| 808 |
+
|
| 809 |
+
[tool.codespell]
|
| 810 |
+
ignore-words-list = "blocs, coo, hist, nd, sav, ser, recuse, nin, timere, expec, expecs"
|
| 811 |
+
ignore-regex = 'https://([\w/\.])+'
|
lib/python3.12/site-packages/pandas/testing.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Public testing utility functions.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from pandas._testing import (
|
| 7 |
+
assert_extension_array_equal,
|
| 8 |
+
assert_frame_equal,
|
| 9 |
+
assert_index_equal,
|
| 10 |
+
assert_series_equal,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
"assert_extension_array_equal",
|
| 15 |
+
"assert_frame_equal",
|
| 16 |
+
"assert_series_equal",
|
| 17 |
+
"assert_index_equal",
|
| 18 |
+
]
|
lib/python3.12/site-packages/pandas/tests/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/pandas/tests/computation/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/pandas/tests/computation/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (203 Bytes). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/computation/__pycache__/test_compat.cpython-312.pyc
ADDED
|
Binary file (1.57 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/computation/test_compat.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas.compat._optional import VERSIONS
|
| 4 |
+
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from pandas.core.computation import expr
|
| 7 |
+
from pandas.core.computation.engines import ENGINES
|
| 8 |
+
from pandas.util.version import Version
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_compat():
|
| 12 |
+
# test we have compat with our version of numexpr
|
| 13 |
+
|
| 14 |
+
from pandas.core.computation.check import NUMEXPR_INSTALLED
|
| 15 |
+
|
| 16 |
+
ne = pytest.importorskip("numexpr")
|
| 17 |
+
|
| 18 |
+
ver = ne.__version__
|
| 19 |
+
if Version(ver) < Version(VERSIONS["numexpr"]):
|
| 20 |
+
assert not NUMEXPR_INSTALLED
|
| 21 |
+
else:
|
| 22 |
+
assert NUMEXPR_INSTALLED
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@pytest.mark.parametrize("engine", ENGINES)
|
| 26 |
+
@pytest.mark.parametrize("parser", expr.PARSERS)
|
| 27 |
+
def test_invalid_numexpr_version(engine, parser):
|
| 28 |
+
if engine == "numexpr":
|
| 29 |
+
pytest.importorskip("numexpr")
|
| 30 |
+
a, b = 1, 2 # noqa: F841
|
| 31 |
+
res = pd.eval("a + b", engine=engine, parser=parser)
|
| 32 |
+
assert res == 3
|
lib/python3.12/site-packages/pandas/tests/computation/test_eval.py
ADDED
|
@@ -0,0 +1,2001 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import reduce
|
| 4 |
+
from itertools import product
|
| 5 |
+
import operator
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pytest
|
| 9 |
+
|
| 10 |
+
from pandas.compat import PY312
|
| 11 |
+
from pandas.errors import (
|
| 12 |
+
NumExprClobberingError,
|
| 13 |
+
PerformanceWarning,
|
| 14 |
+
UndefinedVariableError,
|
| 15 |
+
)
|
| 16 |
+
import pandas.util._test_decorators as td
|
| 17 |
+
|
| 18 |
+
from pandas.core.dtypes.common import (
|
| 19 |
+
is_bool,
|
| 20 |
+
is_float,
|
| 21 |
+
is_list_like,
|
| 22 |
+
is_scalar,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
import pandas as pd
|
| 26 |
+
from pandas import (
|
| 27 |
+
DataFrame,
|
| 28 |
+
Index,
|
| 29 |
+
Series,
|
| 30 |
+
date_range,
|
| 31 |
+
period_range,
|
| 32 |
+
timedelta_range,
|
| 33 |
+
)
|
| 34 |
+
import pandas._testing as tm
|
| 35 |
+
from pandas.core.computation import (
|
| 36 |
+
expr,
|
| 37 |
+
pytables,
|
| 38 |
+
)
|
| 39 |
+
from pandas.core.computation.engines import ENGINES
|
| 40 |
+
from pandas.core.computation.expr import (
|
| 41 |
+
BaseExprVisitor,
|
| 42 |
+
PandasExprVisitor,
|
| 43 |
+
PythonExprVisitor,
|
| 44 |
+
)
|
| 45 |
+
from pandas.core.computation.expressions import (
|
| 46 |
+
NUMEXPR_INSTALLED,
|
| 47 |
+
USE_NUMEXPR,
|
| 48 |
+
)
|
| 49 |
+
from pandas.core.computation.ops import (
|
| 50 |
+
ARITH_OPS_SYMS,
|
| 51 |
+
SPECIAL_CASE_ARITH_OPS_SYMS,
|
| 52 |
+
_binary_math_ops,
|
| 53 |
+
_binary_ops_dict,
|
| 54 |
+
_unary_math_ops,
|
| 55 |
+
)
|
| 56 |
+
from pandas.core.computation.scope import DEFAULT_GLOBALS
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@pytest.fixture(
|
| 60 |
+
params=(
|
| 61 |
+
pytest.param(
|
| 62 |
+
engine,
|
| 63 |
+
marks=[
|
| 64 |
+
pytest.mark.skipif(
|
| 65 |
+
engine == "numexpr" and not USE_NUMEXPR,
|
| 66 |
+
reason=f"numexpr enabled->{USE_NUMEXPR}, "
|
| 67 |
+
f"installed->{NUMEXPR_INSTALLED}",
|
| 68 |
+
),
|
| 69 |
+
td.skip_if_no("numexpr"),
|
| 70 |
+
],
|
| 71 |
+
)
|
| 72 |
+
for engine in ENGINES
|
| 73 |
+
)
|
| 74 |
+
)
|
| 75 |
+
def engine(request):
|
| 76 |
+
return request.param
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@pytest.fixture(params=expr.PARSERS)
|
| 80 |
+
def parser(request):
|
| 81 |
+
return request.param
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _eval_single_bin(lhs, cmp1, rhs, engine):
|
| 85 |
+
c = _binary_ops_dict[cmp1]
|
| 86 |
+
if ENGINES[engine].has_neg_frac:
|
| 87 |
+
try:
|
| 88 |
+
return c(lhs, rhs)
|
| 89 |
+
except ValueError as e:
|
| 90 |
+
if str(e).startswith(
|
| 91 |
+
"negative number cannot be raised to a fractional power"
|
| 92 |
+
):
|
| 93 |
+
return np.nan
|
| 94 |
+
raise
|
| 95 |
+
return c(lhs, rhs)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# TODO: using range(5) here is a kludge
|
| 99 |
+
@pytest.fixture(
|
| 100 |
+
params=list(range(5)),
|
| 101 |
+
ids=["DataFrame", "Series", "SeriesNaN", "DataFrameNaN", "float"],
|
| 102 |
+
)
|
| 103 |
+
def lhs(request):
|
| 104 |
+
nan_df1 = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
|
| 105 |
+
nan_df1[nan_df1 > 0.5] = np.nan
|
| 106 |
+
|
| 107 |
+
opts = (
|
| 108 |
+
DataFrame(np.random.default_rng(2).standard_normal((10, 5))),
|
| 109 |
+
Series(np.random.default_rng(2).standard_normal(5)),
|
| 110 |
+
Series([1, 2, np.nan, np.nan, 5]),
|
| 111 |
+
nan_df1,
|
| 112 |
+
np.random.default_rng(2).standard_normal(),
|
| 113 |
+
)
|
| 114 |
+
return opts[request.param]
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
rhs = lhs
|
| 118 |
+
midhs = lhs
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
@pytest.fixture
|
| 122 |
+
def idx_func_dict():
|
| 123 |
+
return {
|
| 124 |
+
"i": lambda n: Index(np.arange(n), dtype=np.int64),
|
| 125 |
+
"f": lambda n: Index(np.arange(n), dtype=np.float64),
|
| 126 |
+
"s": lambda n: Index([f"{i}_{chr(i)}" for i in range(97, 97 + n)]),
|
| 127 |
+
"dt": lambda n: date_range("2020-01-01", periods=n),
|
| 128 |
+
"td": lambda n: timedelta_range("1 day", periods=n),
|
| 129 |
+
"p": lambda n: period_range("2020-01-01", periods=n, freq="D"),
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class TestEval:
|
| 134 |
+
@pytest.mark.parametrize(
|
| 135 |
+
"cmp1",
|
| 136 |
+
["!=", "==", "<=", ">=", "<", ">"],
|
| 137 |
+
ids=["ne", "eq", "le", "ge", "lt", "gt"],
|
| 138 |
+
)
|
| 139 |
+
@pytest.mark.parametrize("cmp2", [">", "<"], ids=["gt", "lt"])
|
| 140 |
+
@pytest.mark.parametrize("binop", expr.BOOL_OPS_SYMS)
|
| 141 |
+
def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs, engine, parser):
|
| 142 |
+
if parser == "python" and binop in ["and", "or"]:
|
| 143 |
+
msg = "'BoolOp' nodes are not implemented"
|
| 144 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 145 |
+
ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)"
|
| 146 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
lhs_new = _eval_single_bin(lhs, cmp1, rhs, engine)
|
| 150 |
+
rhs_new = _eval_single_bin(lhs, cmp2, rhs, engine)
|
| 151 |
+
expected = _eval_single_bin(lhs_new, binop, rhs_new, engine)
|
| 152 |
+
|
| 153 |
+
ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)"
|
| 154 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 155 |
+
tm.assert_equal(result, expected)
|
| 156 |
+
|
| 157 |
+
@pytest.mark.parametrize("cmp_op", expr.CMP_OPS_SYMS)
|
| 158 |
+
def test_simple_cmp_ops(self, cmp_op, lhs, rhs, engine, parser):
|
| 159 |
+
lhs = lhs < 0
|
| 160 |
+
rhs = rhs < 0
|
| 161 |
+
|
| 162 |
+
if parser == "python" and cmp_op in ["in", "not in"]:
|
| 163 |
+
msg = "'(In|NotIn)' nodes are not implemented"
|
| 164 |
+
|
| 165 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 166 |
+
ex = f"lhs {cmp_op} rhs"
|
| 167 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 168 |
+
return
|
| 169 |
+
|
| 170 |
+
ex = f"lhs {cmp_op} rhs"
|
| 171 |
+
msg = "|".join(
|
| 172 |
+
[
|
| 173 |
+
r"only list-like( or dict-like)? objects are allowed to be "
|
| 174 |
+
r"passed to (DataFrame\.)?isin\(\), you passed a "
|
| 175 |
+
r"(`|')bool(`|')",
|
| 176 |
+
"argument of type 'bool' is not iterable",
|
| 177 |
+
]
|
| 178 |
+
)
|
| 179 |
+
if cmp_op in ("in", "not in") and not is_list_like(rhs):
|
| 180 |
+
with pytest.raises(TypeError, match=msg):
|
| 181 |
+
pd.eval(
|
| 182 |
+
ex,
|
| 183 |
+
engine=engine,
|
| 184 |
+
parser=parser,
|
| 185 |
+
local_dict={"lhs": lhs, "rhs": rhs},
|
| 186 |
+
)
|
| 187 |
+
else:
|
| 188 |
+
expected = _eval_single_bin(lhs, cmp_op, rhs, engine)
|
| 189 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 190 |
+
tm.assert_equal(result, expected)
|
| 191 |
+
|
| 192 |
+
@pytest.mark.parametrize("op", expr.CMP_OPS_SYMS)
|
| 193 |
+
def test_compound_invert_op(self, op, lhs, rhs, request, engine, parser):
|
| 194 |
+
if parser == "python" and op in ["in", "not in"]:
|
| 195 |
+
msg = "'(In|NotIn)' nodes are not implemented"
|
| 196 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 197 |
+
ex = f"~(lhs {op} rhs)"
|
| 198 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 199 |
+
return
|
| 200 |
+
|
| 201 |
+
if (
|
| 202 |
+
is_float(lhs)
|
| 203 |
+
and not is_float(rhs)
|
| 204 |
+
and op in ["in", "not in"]
|
| 205 |
+
and engine == "python"
|
| 206 |
+
and parser == "pandas"
|
| 207 |
+
):
|
| 208 |
+
mark = pytest.mark.xfail(
|
| 209 |
+
reason="Looks like expected is negative, unclear whether "
|
| 210 |
+
"expected is incorrect or result is incorrect"
|
| 211 |
+
)
|
| 212 |
+
request.applymarker(mark)
|
| 213 |
+
skip_these = ["in", "not in"]
|
| 214 |
+
ex = f"~(lhs {op} rhs)"
|
| 215 |
+
|
| 216 |
+
msg = "|".join(
|
| 217 |
+
[
|
| 218 |
+
r"only list-like( or dict-like)? objects are allowed to be "
|
| 219 |
+
r"passed to (DataFrame\.)?isin\(\), you passed a "
|
| 220 |
+
r"(`|')float(`|')",
|
| 221 |
+
"argument of type 'float' is not iterable",
|
| 222 |
+
]
|
| 223 |
+
)
|
| 224 |
+
if is_scalar(rhs) and op in skip_these:
|
| 225 |
+
with pytest.raises(TypeError, match=msg):
|
| 226 |
+
pd.eval(
|
| 227 |
+
ex,
|
| 228 |
+
engine=engine,
|
| 229 |
+
parser=parser,
|
| 230 |
+
local_dict={"lhs": lhs, "rhs": rhs},
|
| 231 |
+
)
|
| 232 |
+
else:
|
| 233 |
+
# compound
|
| 234 |
+
if is_scalar(lhs) and is_scalar(rhs):
|
| 235 |
+
lhs, rhs = (np.array([x]) for x in (lhs, rhs))
|
| 236 |
+
expected = _eval_single_bin(lhs, op, rhs, engine)
|
| 237 |
+
if is_scalar(expected):
|
| 238 |
+
expected = not expected
|
| 239 |
+
else:
|
| 240 |
+
expected = ~expected
|
| 241 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 242 |
+
tm.assert_almost_equal(expected, result)
|
| 243 |
+
|
| 244 |
+
@pytest.mark.parametrize("cmp1", ["<", ">"])
|
| 245 |
+
@pytest.mark.parametrize("cmp2", ["<", ">"])
|
| 246 |
+
def test_chained_cmp_op(self, cmp1, cmp2, lhs, midhs, rhs, engine, parser):
|
| 247 |
+
mid = midhs
|
| 248 |
+
if parser == "python":
|
| 249 |
+
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
|
| 250 |
+
msg = "'BoolOp' nodes are not implemented"
|
| 251 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 252 |
+
pd.eval(ex1, engine=engine, parser=parser)
|
| 253 |
+
return
|
| 254 |
+
|
| 255 |
+
lhs_new = _eval_single_bin(lhs, cmp1, mid, engine)
|
| 256 |
+
rhs_new = _eval_single_bin(mid, cmp2, rhs, engine)
|
| 257 |
+
|
| 258 |
+
if lhs_new is not None and rhs_new is not None:
|
| 259 |
+
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
|
| 260 |
+
ex2 = f"lhs {cmp1} mid and mid {cmp2} rhs"
|
| 261 |
+
ex3 = f"(lhs {cmp1} mid) & (mid {cmp2} rhs)"
|
| 262 |
+
expected = _eval_single_bin(lhs_new, "&", rhs_new, engine)
|
| 263 |
+
|
| 264 |
+
for ex in (ex1, ex2, ex3):
|
| 265 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 266 |
+
|
| 267 |
+
tm.assert_almost_equal(result, expected)
|
| 268 |
+
|
| 269 |
+
@pytest.mark.parametrize(
|
| 270 |
+
"arith1", sorted(set(ARITH_OPS_SYMS).difference(SPECIAL_CASE_ARITH_OPS_SYMS))
|
| 271 |
+
)
|
| 272 |
+
def test_binary_arith_ops(self, arith1, lhs, rhs, engine, parser):
|
| 273 |
+
ex = f"lhs {arith1} rhs"
|
| 274 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 275 |
+
expected = _eval_single_bin(lhs, arith1, rhs, engine)
|
| 276 |
+
|
| 277 |
+
tm.assert_almost_equal(result, expected)
|
| 278 |
+
ex = f"lhs {arith1} rhs {arith1} rhs"
|
| 279 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 280 |
+
nlhs = _eval_single_bin(lhs, arith1, rhs, engine)
|
| 281 |
+
try:
|
| 282 |
+
nlhs, ghs = nlhs.align(rhs)
|
| 283 |
+
except (ValueError, TypeError, AttributeError):
|
| 284 |
+
# ValueError: series frame or frame series align
|
| 285 |
+
# TypeError, AttributeError: series or frame with scalar align
|
| 286 |
+
return
|
| 287 |
+
else:
|
| 288 |
+
if engine == "numexpr":
|
| 289 |
+
import numexpr as ne
|
| 290 |
+
|
| 291 |
+
# direct numpy comparison
|
| 292 |
+
expected = ne.evaluate(f"nlhs {arith1} ghs")
|
| 293 |
+
# Update assert statement due to unreliable numerical
|
| 294 |
+
# precision component (GH37328)
|
| 295 |
+
# TODO: update testing code so that assert_almost_equal statement
|
| 296 |
+
# can be replaced again by the assert_numpy_array_equal statement
|
| 297 |
+
tm.assert_almost_equal(result.values, expected)
|
| 298 |
+
else:
|
| 299 |
+
expected = eval(f"nlhs {arith1} ghs")
|
| 300 |
+
tm.assert_almost_equal(result, expected)
|
| 301 |
+
|
| 302 |
+
# modulus, pow, and floor division require special casing
|
| 303 |
+
|
| 304 |
+
def test_modulus(self, lhs, rhs, engine, parser):
|
| 305 |
+
ex = r"lhs % rhs"
|
| 306 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 307 |
+
expected = lhs % rhs
|
| 308 |
+
tm.assert_almost_equal(result, expected)
|
| 309 |
+
|
| 310 |
+
if engine == "numexpr":
|
| 311 |
+
import numexpr as ne
|
| 312 |
+
|
| 313 |
+
expected = ne.evaluate(r"expected % rhs")
|
| 314 |
+
if isinstance(result, (DataFrame, Series)):
|
| 315 |
+
tm.assert_almost_equal(result.values, expected)
|
| 316 |
+
else:
|
| 317 |
+
tm.assert_almost_equal(result, expected.item())
|
| 318 |
+
else:
|
| 319 |
+
expected = _eval_single_bin(expected, "%", rhs, engine)
|
| 320 |
+
tm.assert_almost_equal(result, expected)
|
| 321 |
+
|
| 322 |
+
def test_floor_division(self, lhs, rhs, engine, parser):
|
| 323 |
+
ex = "lhs // rhs"
|
| 324 |
+
|
| 325 |
+
if engine == "python":
|
| 326 |
+
res = pd.eval(ex, engine=engine, parser=parser)
|
| 327 |
+
expected = lhs // rhs
|
| 328 |
+
tm.assert_equal(res, expected)
|
| 329 |
+
else:
|
| 330 |
+
msg = (
|
| 331 |
+
r"unsupported operand type\(s\) for //: 'VariableNode' and "
|
| 332 |
+
"'VariableNode'"
|
| 333 |
+
)
|
| 334 |
+
with pytest.raises(TypeError, match=msg):
|
| 335 |
+
pd.eval(
|
| 336 |
+
ex,
|
| 337 |
+
local_dict={"lhs": lhs, "rhs": rhs},
|
| 338 |
+
engine=engine,
|
| 339 |
+
parser=parser,
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
@td.skip_if_windows
|
| 343 |
+
def test_pow(self, lhs, rhs, engine, parser):
|
| 344 |
+
# odd failure on win32 platform, so skip
|
| 345 |
+
ex = "lhs ** rhs"
|
| 346 |
+
expected = _eval_single_bin(lhs, "**", rhs, engine)
|
| 347 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 348 |
+
|
| 349 |
+
if (
|
| 350 |
+
is_scalar(lhs)
|
| 351 |
+
and is_scalar(rhs)
|
| 352 |
+
and isinstance(expected, (complex, np.complexfloating))
|
| 353 |
+
and np.isnan(result)
|
| 354 |
+
):
|
| 355 |
+
msg = "(DataFrame.columns|numpy array) are different"
|
| 356 |
+
with pytest.raises(AssertionError, match=msg):
|
| 357 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 358 |
+
else:
|
| 359 |
+
tm.assert_almost_equal(result, expected)
|
| 360 |
+
|
| 361 |
+
ex = "(lhs ** rhs) ** rhs"
|
| 362 |
+
result = pd.eval(ex, engine=engine, parser=parser)
|
| 363 |
+
|
| 364 |
+
middle = _eval_single_bin(lhs, "**", rhs, engine)
|
| 365 |
+
expected = _eval_single_bin(middle, "**", rhs, engine)
|
| 366 |
+
tm.assert_almost_equal(result, expected)
|
| 367 |
+
|
| 368 |
+
def test_check_single_invert_op(self, lhs, engine, parser):
|
| 369 |
+
# simple
|
| 370 |
+
try:
|
| 371 |
+
elb = lhs.astype(bool)
|
| 372 |
+
except AttributeError:
|
| 373 |
+
elb = np.array([bool(lhs)])
|
| 374 |
+
expected = ~elb
|
| 375 |
+
result = pd.eval("~elb", engine=engine, parser=parser)
|
| 376 |
+
tm.assert_almost_equal(expected, result)
|
| 377 |
+
|
| 378 |
+
def test_frame_invert(self, engine, parser):
|
| 379 |
+
expr = "~lhs"
|
| 380 |
+
|
| 381 |
+
# ~ ##
|
| 382 |
+
# frame
|
| 383 |
+
# float always raises
|
| 384 |
+
lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)))
|
| 385 |
+
if engine == "numexpr":
|
| 386 |
+
msg = "couldn't find matching opcode for 'invert_dd'"
|
| 387 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 388 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 389 |
+
else:
|
| 390 |
+
msg = "ufunc 'invert' not supported for the input types"
|
| 391 |
+
with pytest.raises(TypeError, match=msg):
|
| 392 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 393 |
+
|
| 394 |
+
# int raises on numexpr
|
| 395 |
+
lhs = DataFrame(np.random.default_rng(2).integers(5, size=(5, 2)))
|
| 396 |
+
if engine == "numexpr":
|
| 397 |
+
msg = "couldn't find matching opcode for 'invert"
|
| 398 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 399 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 400 |
+
else:
|
| 401 |
+
expect = ~lhs
|
| 402 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 403 |
+
tm.assert_frame_equal(expect, result)
|
| 404 |
+
|
| 405 |
+
# bool always works
|
| 406 |
+
lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)) > 0.5)
|
| 407 |
+
expect = ~lhs
|
| 408 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 409 |
+
tm.assert_frame_equal(expect, result)
|
| 410 |
+
|
| 411 |
+
# object raises
|
| 412 |
+
lhs = DataFrame(
|
| 413 |
+
{"b": ["a", 1, 2.0], "c": np.random.default_rng(2).standard_normal(3) > 0.5}
|
| 414 |
+
)
|
| 415 |
+
if engine == "numexpr":
|
| 416 |
+
with pytest.raises(ValueError, match="unknown type object"):
|
| 417 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 418 |
+
else:
|
| 419 |
+
msg = "bad operand type for unary ~: 'str'"
|
| 420 |
+
with pytest.raises(TypeError, match=msg):
|
| 421 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 422 |
+
|
| 423 |
+
def test_series_invert(self, engine, parser):
|
| 424 |
+
# ~ ####
|
| 425 |
+
expr = "~lhs"
|
| 426 |
+
|
| 427 |
+
# series
|
| 428 |
+
# float raises
|
| 429 |
+
lhs = Series(np.random.default_rng(2).standard_normal(5))
|
| 430 |
+
if engine == "numexpr":
|
| 431 |
+
msg = "couldn't find matching opcode for 'invert_dd'"
|
| 432 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 433 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 434 |
+
else:
|
| 435 |
+
msg = "ufunc 'invert' not supported for the input types"
|
| 436 |
+
with pytest.raises(TypeError, match=msg):
|
| 437 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 438 |
+
|
| 439 |
+
# int raises on numexpr
|
| 440 |
+
lhs = Series(np.random.default_rng(2).integers(5, size=5))
|
| 441 |
+
if engine == "numexpr":
|
| 442 |
+
msg = "couldn't find matching opcode for 'invert"
|
| 443 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 444 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 445 |
+
else:
|
| 446 |
+
expect = ~lhs
|
| 447 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 448 |
+
tm.assert_series_equal(expect, result)
|
| 449 |
+
|
| 450 |
+
# bool
|
| 451 |
+
lhs = Series(np.random.default_rng(2).standard_normal(5) > 0.5)
|
| 452 |
+
expect = ~lhs
|
| 453 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 454 |
+
tm.assert_series_equal(expect, result)
|
| 455 |
+
|
| 456 |
+
# float
|
| 457 |
+
# int
|
| 458 |
+
# bool
|
| 459 |
+
|
| 460 |
+
# object
|
| 461 |
+
lhs = Series(["a", 1, 2.0])
|
| 462 |
+
if engine == "numexpr":
|
| 463 |
+
with pytest.raises(ValueError, match="unknown type object"):
|
| 464 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 465 |
+
else:
|
| 466 |
+
msg = "bad operand type for unary ~: 'str'"
|
| 467 |
+
with pytest.raises(TypeError, match=msg):
|
| 468 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 469 |
+
|
| 470 |
+
def test_frame_negate(self, engine, parser):
|
| 471 |
+
expr = "-lhs"
|
| 472 |
+
|
| 473 |
+
# float
|
| 474 |
+
lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)))
|
| 475 |
+
expect = -lhs
|
| 476 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 477 |
+
tm.assert_frame_equal(expect, result)
|
| 478 |
+
|
| 479 |
+
# int
|
| 480 |
+
lhs = DataFrame(np.random.default_rng(2).integers(5, size=(5, 2)))
|
| 481 |
+
expect = -lhs
|
| 482 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 483 |
+
tm.assert_frame_equal(expect, result)
|
| 484 |
+
|
| 485 |
+
# bool doesn't work with numexpr but works elsewhere
|
| 486 |
+
lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)) > 0.5)
|
| 487 |
+
if engine == "numexpr":
|
| 488 |
+
msg = "couldn't find matching opcode for 'neg_bb'"
|
| 489 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 490 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 491 |
+
else:
|
| 492 |
+
expect = -lhs
|
| 493 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 494 |
+
tm.assert_frame_equal(expect, result)
|
| 495 |
+
|
| 496 |
+
def test_series_negate(self, engine, parser):
|
| 497 |
+
expr = "-lhs"
|
| 498 |
+
|
| 499 |
+
# float
|
| 500 |
+
lhs = Series(np.random.default_rng(2).standard_normal(5))
|
| 501 |
+
expect = -lhs
|
| 502 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 503 |
+
tm.assert_series_equal(expect, result)
|
| 504 |
+
|
| 505 |
+
# int
|
| 506 |
+
lhs = Series(np.random.default_rng(2).integers(5, size=5))
|
| 507 |
+
expect = -lhs
|
| 508 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 509 |
+
tm.assert_series_equal(expect, result)
|
| 510 |
+
|
| 511 |
+
# bool doesn't work with numexpr but works elsewhere
|
| 512 |
+
lhs = Series(np.random.default_rng(2).standard_normal(5) > 0.5)
|
| 513 |
+
if engine == "numexpr":
|
| 514 |
+
msg = "couldn't find matching opcode for 'neg_bb'"
|
| 515 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 516 |
+
pd.eval(expr, engine=engine, parser=parser)
|
| 517 |
+
else:
|
| 518 |
+
expect = -lhs
|
| 519 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 520 |
+
tm.assert_series_equal(expect, result)
|
| 521 |
+
|
| 522 |
+
@pytest.mark.parametrize(
|
| 523 |
+
"lhs",
|
| 524 |
+
[
|
| 525 |
+
# Float
|
| 526 |
+
DataFrame(np.random.default_rng(2).standard_normal((5, 2))),
|
| 527 |
+
# Int
|
| 528 |
+
DataFrame(np.random.default_rng(2).integers(5, size=(5, 2))),
|
| 529 |
+
# bool doesn't work with numexpr but works elsewhere
|
| 530 |
+
DataFrame(np.random.default_rng(2).standard_normal((5, 2)) > 0.5),
|
| 531 |
+
],
|
| 532 |
+
)
|
| 533 |
+
def test_frame_pos(self, lhs, engine, parser):
|
| 534 |
+
expr = "+lhs"
|
| 535 |
+
expect = lhs
|
| 536 |
+
|
| 537 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 538 |
+
tm.assert_frame_equal(expect, result)
|
| 539 |
+
|
| 540 |
+
@pytest.mark.parametrize(
|
| 541 |
+
"lhs",
|
| 542 |
+
[
|
| 543 |
+
# Float
|
| 544 |
+
Series(np.random.default_rng(2).standard_normal(5)),
|
| 545 |
+
# Int
|
| 546 |
+
Series(np.random.default_rng(2).integers(5, size=5)),
|
| 547 |
+
# bool doesn't work with numexpr but works elsewhere
|
| 548 |
+
Series(np.random.default_rng(2).standard_normal(5) > 0.5),
|
| 549 |
+
],
|
| 550 |
+
)
|
| 551 |
+
def test_series_pos(self, lhs, engine, parser):
|
| 552 |
+
expr = "+lhs"
|
| 553 |
+
expect = lhs
|
| 554 |
+
|
| 555 |
+
result = pd.eval(expr, engine=engine, parser=parser)
|
| 556 |
+
tm.assert_series_equal(expect, result)
|
| 557 |
+
|
| 558 |
+
def test_scalar_unary(self, engine, parser):
|
| 559 |
+
msg = "bad operand type for unary ~: 'float'"
|
| 560 |
+
warn = None
|
| 561 |
+
if PY312 and not (engine == "numexpr" and parser == "pandas"):
|
| 562 |
+
warn = DeprecationWarning
|
| 563 |
+
with pytest.raises(TypeError, match=msg):
|
| 564 |
+
pd.eval("~1.0", engine=engine, parser=parser)
|
| 565 |
+
|
| 566 |
+
assert pd.eval("-1.0", parser=parser, engine=engine) == -1.0
|
| 567 |
+
assert pd.eval("+1.0", parser=parser, engine=engine) == +1.0
|
| 568 |
+
assert pd.eval("~1", parser=parser, engine=engine) == ~1
|
| 569 |
+
assert pd.eval("-1", parser=parser, engine=engine) == -1
|
| 570 |
+
assert pd.eval("+1", parser=parser, engine=engine) == +1
|
| 571 |
+
with tm.assert_produces_warning(
|
| 572 |
+
warn, match="Bitwise inversion", check_stacklevel=False
|
| 573 |
+
):
|
| 574 |
+
assert pd.eval("~True", parser=parser, engine=engine) == ~True
|
| 575 |
+
with tm.assert_produces_warning(
|
| 576 |
+
warn, match="Bitwise inversion", check_stacklevel=False
|
| 577 |
+
):
|
| 578 |
+
assert pd.eval("~False", parser=parser, engine=engine) == ~False
|
| 579 |
+
assert pd.eval("-True", parser=parser, engine=engine) == -True
|
| 580 |
+
assert pd.eval("-False", parser=parser, engine=engine) == -False
|
| 581 |
+
assert pd.eval("+True", parser=parser, engine=engine) == +True
|
| 582 |
+
assert pd.eval("+False", parser=parser, engine=engine) == +False
|
| 583 |
+
|
| 584 |
+
def test_unary_in_array(self):
|
| 585 |
+
# GH 11235
|
| 586 |
+
# TODO: 2022-01-29: result return list with numexpr 2.7.3 in CI
|
| 587 |
+
# but cannot reproduce locally
|
| 588 |
+
result = np.array(
|
| 589 |
+
pd.eval("[-True, True, +True, -False, False, +False, -37, 37, ~37, +37]"),
|
| 590 |
+
dtype=np.object_,
|
| 591 |
+
)
|
| 592 |
+
expected = np.array(
|
| 593 |
+
[
|
| 594 |
+
-True,
|
| 595 |
+
True,
|
| 596 |
+
+True,
|
| 597 |
+
-False,
|
| 598 |
+
False,
|
| 599 |
+
+False,
|
| 600 |
+
-37,
|
| 601 |
+
37,
|
| 602 |
+
~37,
|
| 603 |
+
+37,
|
| 604 |
+
],
|
| 605 |
+
dtype=np.object_,
|
| 606 |
+
)
|
| 607 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 608 |
+
|
| 609 |
+
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
|
| 610 |
+
@pytest.mark.parametrize("expr", ["x < -0.1", "-5 > x"])
|
| 611 |
+
def test_float_comparison_bin_op(self, dtype, expr):
|
| 612 |
+
# GH 16363
|
| 613 |
+
df = DataFrame({"x": np.array([0], dtype=dtype)})
|
| 614 |
+
res = df.eval(expr)
|
| 615 |
+
assert res.values == np.array([False])
|
| 616 |
+
|
| 617 |
+
def test_unary_in_function(self):
|
| 618 |
+
# GH 46471
|
| 619 |
+
df = DataFrame({"x": [0, 1, np.nan]})
|
| 620 |
+
|
| 621 |
+
result = df.eval("x.fillna(-1)")
|
| 622 |
+
expected = df.x.fillna(-1)
|
| 623 |
+
# column name becomes None if using numexpr
|
| 624 |
+
# only check names when the engine is not numexpr
|
| 625 |
+
tm.assert_series_equal(result, expected, check_names=not USE_NUMEXPR)
|
| 626 |
+
|
| 627 |
+
result = df.eval("x.shift(1, fill_value=-1)")
|
| 628 |
+
expected = df.x.shift(1, fill_value=-1)
|
| 629 |
+
tm.assert_series_equal(result, expected, check_names=not USE_NUMEXPR)
|
| 630 |
+
|
| 631 |
+
@pytest.mark.parametrize(
|
| 632 |
+
"ex",
|
| 633 |
+
(
|
| 634 |
+
"1 or 2",
|
| 635 |
+
"1 and 2",
|
| 636 |
+
"a and b",
|
| 637 |
+
"a or b",
|
| 638 |
+
"1 or 2 and (3 + 2) > 3",
|
| 639 |
+
"2 * x > 2 or 1 and 2",
|
| 640 |
+
"2 * df > 3 and 1 or a",
|
| 641 |
+
),
|
| 642 |
+
)
|
| 643 |
+
def test_disallow_scalar_bool_ops(self, ex, engine, parser):
|
| 644 |
+
x, a, b = np.random.default_rng(2).standard_normal(3), 1, 2 # noqa: F841
|
| 645 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((3, 2))) # noqa: F841
|
| 646 |
+
|
| 647 |
+
msg = "cannot evaluate scalar only bool ops|'BoolOp' nodes are not"
|
| 648 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 649 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 650 |
+
|
| 651 |
+
def test_identical(self, engine, parser):
|
| 652 |
+
# see gh-10546
|
| 653 |
+
x = 1
|
| 654 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 655 |
+
assert result == 1
|
| 656 |
+
assert is_scalar(result)
|
| 657 |
+
|
| 658 |
+
x = 1.5
|
| 659 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 660 |
+
assert result == 1.5
|
| 661 |
+
assert is_scalar(result)
|
| 662 |
+
|
| 663 |
+
x = False
|
| 664 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 665 |
+
assert not result
|
| 666 |
+
assert is_bool(result)
|
| 667 |
+
assert is_scalar(result)
|
| 668 |
+
|
| 669 |
+
x = np.array([1])
|
| 670 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 671 |
+
tm.assert_numpy_array_equal(result, np.array([1]))
|
| 672 |
+
assert result.shape == (1,)
|
| 673 |
+
|
| 674 |
+
x = np.array([1.5])
|
| 675 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 676 |
+
tm.assert_numpy_array_equal(result, np.array([1.5]))
|
| 677 |
+
assert result.shape == (1,)
|
| 678 |
+
|
| 679 |
+
x = np.array([False]) # noqa: F841
|
| 680 |
+
result = pd.eval("x", engine=engine, parser=parser)
|
| 681 |
+
tm.assert_numpy_array_equal(result, np.array([False]))
|
| 682 |
+
assert result.shape == (1,)
|
| 683 |
+
|
| 684 |
+
def test_line_continuation(self, engine, parser):
|
| 685 |
+
# GH 11149
|
| 686 |
+
exp = """1 + 2 * \
|
| 687 |
+
5 - 1 + 2 """
|
| 688 |
+
result = pd.eval(exp, engine=engine, parser=parser)
|
| 689 |
+
assert result == 12
|
| 690 |
+
|
| 691 |
+
def test_float_truncation(self, engine, parser):
|
| 692 |
+
# GH 14241
|
| 693 |
+
exp = "1000000000.006"
|
| 694 |
+
result = pd.eval(exp, engine=engine, parser=parser)
|
| 695 |
+
expected = np.float64(exp)
|
| 696 |
+
assert result == expected
|
| 697 |
+
|
| 698 |
+
df = DataFrame({"A": [1000000000.0009, 1000000000.0011, 1000000000.0015]})
|
| 699 |
+
cutoff = 1000000000.0006
|
| 700 |
+
result = df.query(f"A < {cutoff:.4f}")
|
| 701 |
+
assert result.empty
|
| 702 |
+
|
| 703 |
+
cutoff = 1000000000.0010
|
| 704 |
+
result = df.query(f"A > {cutoff:.4f}")
|
| 705 |
+
expected = df.loc[[1, 2], :]
|
| 706 |
+
tm.assert_frame_equal(expected, result)
|
| 707 |
+
|
| 708 |
+
exact = 1000000000.0011
|
| 709 |
+
result = df.query(f"A == {exact:.4f}")
|
| 710 |
+
expected = df.loc[[1], :]
|
| 711 |
+
tm.assert_frame_equal(expected, result)
|
| 712 |
+
|
| 713 |
+
def test_disallow_python_keywords(self):
|
| 714 |
+
# GH 18221
|
| 715 |
+
df = DataFrame([[0, 0, 0]], columns=["foo", "bar", "class"])
|
| 716 |
+
msg = "Python keyword not valid identifier in numexpr query"
|
| 717 |
+
with pytest.raises(SyntaxError, match=msg):
|
| 718 |
+
df.query("class == 0")
|
| 719 |
+
|
| 720 |
+
df = DataFrame()
|
| 721 |
+
df.index.name = "lambda"
|
| 722 |
+
with pytest.raises(SyntaxError, match=msg):
|
| 723 |
+
df.query("lambda == 0")
|
| 724 |
+
|
| 725 |
+
def test_true_false_logic(self):
|
| 726 |
+
# GH 25823
|
| 727 |
+
# This behavior is deprecated in Python 3.12
|
| 728 |
+
with tm.maybe_produces_warning(
|
| 729 |
+
DeprecationWarning, PY312, check_stacklevel=False
|
| 730 |
+
):
|
| 731 |
+
assert pd.eval("not True") == -2
|
| 732 |
+
assert pd.eval("not False") == -1
|
| 733 |
+
assert pd.eval("True and not True") == 0
|
| 734 |
+
|
| 735 |
+
def test_and_logic_string_match(self):
|
| 736 |
+
# GH 25823
|
| 737 |
+
event = Series({"a": "hello"})
|
| 738 |
+
assert pd.eval(f"{event.str.match('hello').a}")
|
| 739 |
+
assert pd.eval(f"{event.str.match('hello').a and event.str.match('hello').a}")
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
# -------------------------------------
|
| 743 |
+
# gh-12388: Typecasting rules consistency with python
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
class TestTypeCasting:
|
| 747 |
+
@pytest.mark.parametrize("op", ["+", "-", "*", "**", "/"])
|
| 748 |
+
# maybe someday... numexpr has too many upcasting rules now
|
| 749 |
+
# chain(*(np.core.sctypes[x] for x in ['uint', 'int', 'float']))
|
| 750 |
+
@pytest.mark.parametrize("left_right", [("df", "3"), ("3", "df")])
|
| 751 |
+
def test_binop_typecasting(
|
| 752 |
+
self, engine, parser, op, complex_or_float_dtype, left_right, request
|
| 753 |
+
):
|
| 754 |
+
# GH#21374
|
| 755 |
+
dtype = complex_or_float_dtype
|
| 756 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dtype)
|
| 757 |
+
left, right = left_right
|
| 758 |
+
s = f"{left} {op} {right}"
|
| 759 |
+
res = pd.eval(s, engine=engine, parser=parser)
|
| 760 |
+
if dtype == "complex64" and engine == "numexpr":
|
| 761 |
+
mark = pytest.mark.xfail(
|
| 762 |
+
reason="numexpr issue with complex that are upcast "
|
| 763 |
+
"to complex 128 "
|
| 764 |
+
"https://github.com/pydata/numexpr/issues/492"
|
| 765 |
+
)
|
| 766 |
+
request.applymarker(mark)
|
| 767 |
+
assert df.values.dtype == dtype
|
| 768 |
+
assert res.values.dtype == dtype
|
| 769 |
+
tm.assert_frame_equal(res, eval(s), check_exact=False)
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
# -------------------------------------
|
| 773 |
+
# Basic and complex alignment
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def should_warn(*args):
|
| 777 |
+
not_mono = not any(map(operator.attrgetter("is_monotonic_increasing"), args))
|
| 778 |
+
only_one_dt = reduce(
|
| 779 |
+
operator.xor, (issubclass(x.dtype.type, np.datetime64) for x in args)
|
| 780 |
+
)
|
| 781 |
+
return not_mono and only_one_dt
|
| 782 |
+
|
| 783 |
+
|
| 784 |
+
class TestAlignment:
|
| 785 |
+
index_types = ["i", "s", "dt"]
|
| 786 |
+
lhs_index_types = index_types + ["s"] # 'p'
|
| 787 |
+
|
| 788 |
+
def test_align_nested_unary_op(self, engine, parser):
|
| 789 |
+
s = "df * ~2"
|
| 790 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
| 791 |
+
res = pd.eval(s, engine=engine, parser=parser)
|
| 792 |
+
tm.assert_frame_equal(res, df * ~2)
|
| 793 |
+
|
| 794 |
+
@pytest.mark.filterwarnings("always::RuntimeWarning")
|
| 795 |
+
@pytest.mark.parametrize("lr_idx_type", lhs_index_types)
|
| 796 |
+
@pytest.mark.parametrize("rr_idx_type", index_types)
|
| 797 |
+
@pytest.mark.parametrize("c_idx_type", index_types)
|
| 798 |
+
def test_basic_frame_alignment(
|
| 799 |
+
self, engine, parser, lr_idx_type, rr_idx_type, c_idx_type, idx_func_dict
|
| 800 |
+
):
|
| 801 |
+
df = DataFrame(
|
| 802 |
+
np.random.default_rng(2).standard_normal((10, 10)),
|
| 803 |
+
index=idx_func_dict[lr_idx_type](10),
|
| 804 |
+
columns=idx_func_dict[c_idx_type](10),
|
| 805 |
+
)
|
| 806 |
+
df2 = DataFrame(
|
| 807 |
+
np.random.default_rng(2).standard_normal((20, 10)),
|
| 808 |
+
index=idx_func_dict[rr_idx_type](20),
|
| 809 |
+
columns=idx_func_dict[c_idx_type](10),
|
| 810 |
+
)
|
| 811 |
+
# only warns if not monotonic and not sortable
|
| 812 |
+
if should_warn(df.index, df2.index):
|
| 813 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 814 |
+
res = pd.eval("df + df2", engine=engine, parser=parser)
|
| 815 |
+
else:
|
| 816 |
+
res = pd.eval("df + df2", engine=engine, parser=parser)
|
| 817 |
+
tm.assert_frame_equal(res, df + df2)
|
| 818 |
+
|
| 819 |
+
@pytest.mark.parametrize("r_idx_type", lhs_index_types)
|
| 820 |
+
@pytest.mark.parametrize("c_idx_type", lhs_index_types)
|
| 821 |
+
def test_frame_comparison(
|
| 822 |
+
self, engine, parser, r_idx_type, c_idx_type, idx_func_dict
|
| 823 |
+
):
|
| 824 |
+
df = DataFrame(
|
| 825 |
+
np.random.default_rng(2).standard_normal((10, 10)),
|
| 826 |
+
index=idx_func_dict[r_idx_type](10),
|
| 827 |
+
columns=idx_func_dict[c_idx_type](10),
|
| 828 |
+
)
|
| 829 |
+
res = pd.eval("df < 2", engine=engine, parser=parser)
|
| 830 |
+
tm.assert_frame_equal(res, df < 2)
|
| 831 |
+
|
| 832 |
+
df3 = DataFrame(
|
| 833 |
+
np.random.default_rng(2).standard_normal(df.shape),
|
| 834 |
+
index=df.index,
|
| 835 |
+
columns=df.columns,
|
| 836 |
+
)
|
| 837 |
+
res = pd.eval("df < df3", engine=engine, parser=parser)
|
| 838 |
+
tm.assert_frame_equal(res, df < df3)
|
| 839 |
+
|
| 840 |
+
@pytest.mark.filterwarnings("ignore::RuntimeWarning")
|
| 841 |
+
@pytest.mark.parametrize("r1", lhs_index_types)
|
| 842 |
+
@pytest.mark.parametrize("c1", index_types)
|
| 843 |
+
@pytest.mark.parametrize("r2", index_types)
|
| 844 |
+
@pytest.mark.parametrize("c2", index_types)
|
| 845 |
+
def test_medium_complex_frame_alignment(
|
| 846 |
+
self, engine, parser, r1, c1, r2, c2, idx_func_dict
|
| 847 |
+
):
|
| 848 |
+
df = DataFrame(
|
| 849 |
+
np.random.default_rng(2).standard_normal((3, 2)),
|
| 850 |
+
index=idx_func_dict[r1](3),
|
| 851 |
+
columns=idx_func_dict[c1](2),
|
| 852 |
+
)
|
| 853 |
+
df2 = DataFrame(
|
| 854 |
+
np.random.default_rng(2).standard_normal((4, 2)),
|
| 855 |
+
index=idx_func_dict[r2](4),
|
| 856 |
+
columns=idx_func_dict[c2](2),
|
| 857 |
+
)
|
| 858 |
+
df3 = DataFrame(
|
| 859 |
+
np.random.default_rng(2).standard_normal((5, 2)),
|
| 860 |
+
index=idx_func_dict[r2](5),
|
| 861 |
+
columns=idx_func_dict[c2](2),
|
| 862 |
+
)
|
| 863 |
+
if should_warn(df.index, df2.index, df3.index):
|
| 864 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 865 |
+
res = pd.eval("df + df2 + df3", engine=engine, parser=parser)
|
| 866 |
+
else:
|
| 867 |
+
res = pd.eval("df + df2 + df3", engine=engine, parser=parser)
|
| 868 |
+
tm.assert_frame_equal(res, df + df2 + df3)
|
| 869 |
+
|
| 870 |
+
@pytest.mark.filterwarnings("ignore::RuntimeWarning")
|
| 871 |
+
@pytest.mark.parametrize("index_name", ["index", "columns"])
|
| 872 |
+
@pytest.mark.parametrize("c_idx_type", index_types)
|
| 873 |
+
@pytest.mark.parametrize("r_idx_type", lhs_index_types)
|
| 874 |
+
def test_basic_frame_series_alignment(
|
| 875 |
+
self, engine, parser, index_name, r_idx_type, c_idx_type, idx_func_dict
|
| 876 |
+
):
|
| 877 |
+
df = DataFrame(
|
| 878 |
+
np.random.default_rng(2).standard_normal((10, 10)),
|
| 879 |
+
index=idx_func_dict[r_idx_type](10),
|
| 880 |
+
columns=idx_func_dict[c_idx_type](10),
|
| 881 |
+
)
|
| 882 |
+
index = getattr(df, index_name)
|
| 883 |
+
s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
|
| 884 |
+
|
| 885 |
+
if should_warn(df.index, s.index):
|
| 886 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 887 |
+
res = pd.eval("df + s", engine=engine, parser=parser)
|
| 888 |
+
else:
|
| 889 |
+
res = pd.eval("df + s", engine=engine, parser=parser)
|
| 890 |
+
|
| 891 |
+
if r_idx_type == "dt" or c_idx_type == "dt":
|
| 892 |
+
expected = df.add(s) if engine == "numexpr" else df + s
|
| 893 |
+
else:
|
| 894 |
+
expected = df + s
|
| 895 |
+
tm.assert_frame_equal(res, expected)
|
| 896 |
+
|
| 897 |
+
@pytest.mark.parametrize("index_name", ["index", "columns"])
|
| 898 |
+
@pytest.mark.parametrize(
|
| 899 |
+
"r_idx_type, c_idx_type",
|
| 900 |
+
list(product(["i", "s"], ["i", "s"])) + [("dt", "dt")],
|
| 901 |
+
)
|
| 902 |
+
@pytest.mark.filterwarnings("ignore::RuntimeWarning")
|
| 903 |
+
def test_basic_series_frame_alignment(
|
| 904 |
+
self, request, engine, parser, index_name, r_idx_type, c_idx_type, idx_func_dict
|
| 905 |
+
):
|
| 906 |
+
if (
|
| 907 |
+
engine == "numexpr"
|
| 908 |
+
and parser in ("pandas", "python")
|
| 909 |
+
and index_name == "index"
|
| 910 |
+
and r_idx_type == "i"
|
| 911 |
+
and c_idx_type == "s"
|
| 912 |
+
):
|
| 913 |
+
reason = (
|
| 914 |
+
f"Flaky column ordering when engine={engine}, "
|
| 915 |
+
f"parser={parser}, index_name={index_name}, "
|
| 916 |
+
f"r_idx_type={r_idx_type}, c_idx_type={c_idx_type}"
|
| 917 |
+
)
|
| 918 |
+
request.applymarker(pytest.mark.xfail(reason=reason, strict=False))
|
| 919 |
+
df = DataFrame(
|
| 920 |
+
np.random.default_rng(2).standard_normal((10, 7)),
|
| 921 |
+
index=idx_func_dict[r_idx_type](10),
|
| 922 |
+
columns=idx_func_dict[c_idx_type](7),
|
| 923 |
+
)
|
| 924 |
+
index = getattr(df, index_name)
|
| 925 |
+
s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
|
| 926 |
+
if should_warn(s.index, df.index):
|
| 927 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 928 |
+
res = pd.eval("s + df", engine=engine, parser=parser)
|
| 929 |
+
else:
|
| 930 |
+
res = pd.eval("s + df", engine=engine, parser=parser)
|
| 931 |
+
|
| 932 |
+
if r_idx_type == "dt" or c_idx_type == "dt":
|
| 933 |
+
expected = df.add(s) if engine == "numexpr" else s + df
|
| 934 |
+
else:
|
| 935 |
+
expected = s + df
|
| 936 |
+
tm.assert_frame_equal(res, expected)
|
| 937 |
+
|
| 938 |
+
@pytest.mark.filterwarnings("ignore::RuntimeWarning")
|
| 939 |
+
@pytest.mark.parametrize("c_idx_type", index_types)
|
| 940 |
+
@pytest.mark.parametrize("r_idx_type", lhs_index_types)
|
| 941 |
+
@pytest.mark.parametrize("index_name", ["index", "columns"])
|
| 942 |
+
@pytest.mark.parametrize("op", ["+", "*"])
|
| 943 |
+
def test_series_frame_commutativity(
|
| 944 |
+
self, engine, parser, index_name, op, r_idx_type, c_idx_type, idx_func_dict
|
| 945 |
+
):
|
| 946 |
+
df = DataFrame(
|
| 947 |
+
np.random.default_rng(2).standard_normal((10, 10)),
|
| 948 |
+
index=idx_func_dict[r_idx_type](10),
|
| 949 |
+
columns=idx_func_dict[c_idx_type](10),
|
| 950 |
+
)
|
| 951 |
+
index = getattr(df, index_name)
|
| 952 |
+
s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
|
| 953 |
+
|
| 954 |
+
lhs = f"s {op} df"
|
| 955 |
+
rhs = f"df {op} s"
|
| 956 |
+
if should_warn(df.index, s.index):
|
| 957 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 958 |
+
a = pd.eval(lhs, engine=engine, parser=parser)
|
| 959 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 960 |
+
b = pd.eval(rhs, engine=engine, parser=parser)
|
| 961 |
+
else:
|
| 962 |
+
a = pd.eval(lhs, engine=engine, parser=parser)
|
| 963 |
+
b = pd.eval(rhs, engine=engine, parser=parser)
|
| 964 |
+
|
| 965 |
+
if r_idx_type != "dt" and c_idx_type != "dt":
|
| 966 |
+
if engine == "numexpr":
|
| 967 |
+
tm.assert_frame_equal(a, b)
|
| 968 |
+
|
| 969 |
+
@pytest.mark.filterwarnings("always::RuntimeWarning")
|
| 970 |
+
@pytest.mark.parametrize("r1", lhs_index_types)
|
| 971 |
+
@pytest.mark.parametrize("c1", index_types)
|
| 972 |
+
@pytest.mark.parametrize("r2", index_types)
|
| 973 |
+
@pytest.mark.parametrize("c2", index_types)
|
| 974 |
+
def test_complex_series_frame_alignment(
|
| 975 |
+
self, engine, parser, r1, c1, r2, c2, idx_func_dict
|
| 976 |
+
):
|
| 977 |
+
n = 3
|
| 978 |
+
m1 = 5
|
| 979 |
+
m2 = 2 * m1
|
| 980 |
+
df = DataFrame(
|
| 981 |
+
np.random.default_rng(2).standard_normal((m1, n)),
|
| 982 |
+
index=idx_func_dict[r1](m1),
|
| 983 |
+
columns=idx_func_dict[c1](n),
|
| 984 |
+
)
|
| 985 |
+
df2 = DataFrame(
|
| 986 |
+
np.random.default_rng(2).standard_normal((m2, n)),
|
| 987 |
+
index=idx_func_dict[r2](m2),
|
| 988 |
+
columns=idx_func_dict[c2](n),
|
| 989 |
+
)
|
| 990 |
+
index = df2.columns
|
| 991 |
+
ser = Series(np.random.default_rng(2).standard_normal(n), index[:n])
|
| 992 |
+
|
| 993 |
+
if r2 == "dt" or c2 == "dt":
|
| 994 |
+
if engine == "numexpr":
|
| 995 |
+
expected2 = df2.add(ser)
|
| 996 |
+
else:
|
| 997 |
+
expected2 = df2 + ser
|
| 998 |
+
else:
|
| 999 |
+
expected2 = df2 + ser
|
| 1000 |
+
|
| 1001 |
+
if r1 == "dt" or c1 == "dt":
|
| 1002 |
+
if engine == "numexpr":
|
| 1003 |
+
expected = expected2.add(df)
|
| 1004 |
+
else:
|
| 1005 |
+
expected = expected2 + df
|
| 1006 |
+
else:
|
| 1007 |
+
expected = expected2 + df
|
| 1008 |
+
|
| 1009 |
+
if should_warn(df2.index, ser.index, df.index):
|
| 1010 |
+
with tm.assert_produces_warning(RuntimeWarning):
|
| 1011 |
+
res = pd.eval("df2 + ser + df", engine=engine, parser=parser)
|
| 1012 |
+
else:
|
| 1013 |
+
res = pd.eval("df2 + ser + df", engine=engine, parser=parser)
|
| 1014 |
+
assert res.shape == expected.shape
|
| 1015 |
+
tm.assert_frame_equal(res, expected)
|
| 1016 |
+
|
| 1017 |
+
def test_performance_warning_for_poor_alignment(self, engine, parser):
|
| 1018 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((1000, 10)))
|
| 1019 |
+
s = Series(np.random.default_rng(2).standard_normal(10000))
|
| 1020 |
+
if engine == "numexpr":
|
| 1021 |
+
seen = PerformanceWarning
|
| 1022 |
+
else:
|
| 1023 |
+
seen = False
|
| 1024 |
+
|
| 1025 |
+
with tm.assert_produces_warning(seen):
|
| 1026 |
+
pd.eval("df + s", engine=engine, parser=parser)
|
| 1027 |
+
|
| 1028 |
+
s = Series(np.random.default_rng(2).standard_normal(1000))
|
| 1029 |
+
with tm.assert_produces_warning(False):
|
| 1030 |
+
pd.eval("df + s", engine=engine, parser=parser)
|
| 1031 |
+
|
| 1032 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((10, 10000)))
|
| 1033 |
+
s = Series(np.random.default_rng(2).standard_normal(10000))
|
| 1034 |
+
with tm.assert_produces_warning(False):
|
| 1035 |
+
pd.eval("df + s", engine=engine, parser=parser)
|
| 1036 |
+
|
| 1037 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((10, 10)))
|
| 1038 |
+
s = Series(np.random.default_rng(2).standard_normal(10000))
|
| 1039 |
+
|
| 1040 |
+
is_python_engine = engine == "python"
|
| 1041 |
+
|
| 1042 |
+
if not is_python_engine:
|
| 1043 |
+
wrn = PerformanceWarning
|
| 1044 |
+
else:
|
| 1045 |
+
wrn = False
|
| 1046 |
+
|
| 1047 |
+
with tm.assert_produces_warning(wrn) as w:
|
| 1048 |
+
pd.eval("df + s", engine=engine, parser=parser)
|
| 1049 |
+
|
| 1050 |
+
if not is_python_engine:
|
| 1051 |
+
assert len(w) == 1
|
| 1052 |
+
msg = str(w[0].message)
|
| 1053 |
+
logged = np.log10(s.size - df.shape[1])
|
| 1054 |
+
expected = (
|
| 1055 |
+
f"Alignment difference on axis 1 is larger "
|
| 1056 |
+
f"than an order of magnitude on term 'df', "
|
| 1057 |
+
f"by more than {logged:.4g}; performance may suffer."
|
| 1058 |
+
)
|
| 1059 |
+
assert msg == expected
|
| 1060 |
+
|
| 1061 |
+
|
| 1062 |
+
# ------------------------------------
|
| 1063 |
+
# Slightly more complex ops
|
| 1064 |
+
|
| 1065 |
+
|
| 1066 |
+
class TestOperations:
|
| 1067 |
+
def eval(self, *args, **kwargs):
|
| 1068 |
+
kwargs["level"] = kwargs.pop("level", 0) + 1
|
| 1069 |
+
return pd.eval(*args, **kwargs)
|
| 1070 |
+
|
| 1071 |
+
def test_simple_arith_ops(self, engine, parser):
|
| 1072 |
+
exclude_arith = []
|
| 1073 |
+
if parser == "python":
|
| 1074 |
+
exclude_arith = ["in", "not in"]
|
| 1075 |
+
|
| 1076 |
+
arith_ops = [
|
| 1077 |
+
op
|
| 1078 |
+
for op in expr.ARITH_OPS_SYMS + expr.CMP_OPS_SYMS
|
| 1079 |
+
if op not in exclude_arith
|
| 1080 |
+
]
|
| 1081 |
+
|
| 1082 |
+
ops = (op for op in arith_ops if op != "//")
|
| 1083 |
+
|
| 1084 |
+
for op in ops:
|
| 1085 |
+
ex = f"1 {op} 1"
|
| 1086 |
+
ex2 = f"x {op} 1"
|
| 1087 |
+
ex3 = f"1 {op} (x + 1)"
|
| 1088 |
+
|
| 1089 |
+
if op in ("in", "not in"):
|
| 1090 |
+
msg = "argument of type 'int' is not iterable"
|
| 1091 |
+
with pytest.raises(TypeError, match=msg):
|
| 1092 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 1093 |
+
else:
|
| 1094 |
+
expec = _eval_single_bin(1, op, 1, engine)
|
| 1095 |
+
x = self.eval(ex, engine=engine, parser=parser)
|
| 1096 |
+
assert x == expec
|
| 1097 |
+
|
| 1098 |
+
expec = _eval_single_bin(x, op, 1, engine)
|
| 1099 |
+
y = self.eval(ex2, local_dict={"x": x}, engine=engine, parser=parser)
|
| 1100 |
+
assert y == expec
|
| 1101 |
+
|
| 1102 |
+
expec = _eval_single_bin(1, op, x + 1, engine)
|
| 1103 |
+
y = self.eval(ex3, local_dict={"x": x}, engine=engine, parser=parser)
|
| 1104 |
+
assert y == expec
|
| 1105 |
+
|
| 1106 |
+
@pytest.mark.parametrize("rhs", [True, False])
|
| 1107 |
+
@pytest.mark.parametrize("lhs", [True, False])
|
| 1108 |
+
@pytest.mark.parametrize("op", expr.BOOL_OPS_SYMS)
|
| 1109 |
+
def test_simple_bool_ops(self, rhs, lhs, op):
|
| 1110 |
+
ex = f"{lhs} {op} {rhs}"
|
| 1111 |
+
|
| 1112 |
+
if parser == "python" and op in ["and", "or"]:
|
| 1113 |
+
msg = "'BoolOp' nodes are not implemented"
|
| 1114 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1115 |
+
self.eval(ex)
|
| 1116 |
+
return
|
| 1117 |
+
|
| 1118 |
+
res = self.eval(ex)
|
| 1119 |
+
exp = eval(ex)
|
| 1120 |
+
assert res == exp
|
| 1121 |
+
|
| 1122 |
+
@pytest.mark.parametrize("rhs", [True, False])
|
| 1123 |
+
@pytest.mark.parametrize("lhs", [True, False])
|
| 1124 |
+
@pytest.mark.parametrize("op", expr.BOOL_OPS_SYMS)
|
| 1125 |
+
def test_bool_ops_with_constants(self, rhs, lhs, op):
|
| 1126 |
+
ex = f"{lhs} {op} {rhs}"
|
| 1127 |
+
|
| 1128 |
+
if parser == "python" and op in ["and", "or"]:
|
| 1129 |
+
msg = "'BoolOp' nodes are not implemented"
|
| 1130 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1131 |
+
self.eval(ex)
|
| 1132 |
+
return
|
| 1133 |
+
|
| 1134 |
+
res = self.eval(ex)
|
| 1135 |
+
exp = eval(ex)
|
| 1136 |
+
assert res == exp
|
| 1137 |
+
|
| 1138 |
+
def test_4d_ndarray_fails(self):
|
| 1139 |
+
x = np.random.default_rng(2).standard_normal((3, 4, 5, 6))
|
| 1140 |
+
y = Series(np.random.default_rng(2).standard_normal(10))
|
| 1141 |
+
msg = "N-dimensional objects, where N > 2, are not supported with eval"
|
| 1142 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1143 |
+
self.eval("x + y", local_dict={"x": x, "y": y})
|
| 1144 |
+
|
| 1145 |
+
def test_constant(self):
|
| 1146 |
+
x = self.eval("1")
|
| 1147 |
+
assert x == 1
|
| 1148 |
+
|
| 1149 |
+
def test_single_variable(self):
|
| 1150 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
|
| 1151 |
+
df2 = self.eval("df", local_dict={"df": df})
|
| 1152 |
+
tm.assert_frame_equal(df, df2)
|
| 1153 |
+
|
| 1154 |
+
def test_failing_subscript_with_name_error(self):
|
| 1155 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3))) # noqa: F841
|
| 1156 |
+
with pytest.raises(NameError, match="name 'x' is not defined"):
|
| 1157 |
+
self.eval("df[x > 2] > 2")
|
| 1158 |
+
|
| 1159 |
+
def test_lhs_expression_subscript(self):
|
| 1160 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
| 1161 |
+
result = self.eval("(df + 1)[df > 2]", local_dict={"df": df})
|
| 1162 |
+
expected = (df + 1)[df > 2]
|
| 1163 |
+
tm.assert_frame_equal(result, expected)
|
| 1164 |
+
|
| 1165 |
+
def test_attr_expression(self):
|
| 1166 |
+
df = DataFrame(
|
| 1167 |
+
np.random.default_rng(2).standard_normal((5, 3)), columns=list("abc")
|
| 1168 |
+
)
|
| 1169 |
+
expr1 = "df.a < df.b"
|
| 1170 |
+
expec1 = df.a < df.b
|
| 1171 |
+
expr2 = "df.a + df.b + df.c"
|
| 1172 |
+
expec2 = df.a + df.b + df.c
|
| 1173 |
+
expr3 = "df.a + df.b + df.c[df.b < 0]"
|
| 1174 |
+
expec3 = df.a + df.b + df.c[df.b < 0]
|
| 1175 |
+
exprs = expr1, expr2, expr3
|
| 1176 |
+
expecs = expec1, expec2, expec3
|
| 1177 |
+
for e, expec in zip(exprs, expecs):
|
| 1178 |
+
tm.assert_series_equal(expec, self.eval(e, local_dict={"df": df}))
|
| 1179 |
+
|
| 1180 |
+
def test_assignment_fails(self):
|
| 1181 |
+
df = DataFrame(
|
| 1182 |
+
np.random.default_rng(2).standard_normal((5, 3)), columns=list("abc")
|
| 1183 |
+
)
|
| 1184 |
+
df2 = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
| 1185 |
+
expr1 = "df = df2"
|
| 1186 |
+
msg = "cannot assign without a target object"
|
| 1187 |
+
with pytest.raises(ValueError, match=msg):
|
| 1188 |
+
self.eval(expr1, local_dict={"df": df, "df2": df2})
|
| 1189 |
+
|
| 1190 |
+
def test_assignment_column_multiple_raise(self):
|
| 1191 |
+
df = DataFrame(
|
| 1192 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1193 |
+
)
|
| 1194 |
+
# multiple assignees
|
| 1195 |
+
with pytest.raises(SyntaxError, match="invalid syntax"):
|
| 1196 |
+
df.eval("d c = a + b")
|
| 1197 |
+
|
| 1198 |
+
def test_assignment_column_invalid_assign(self):
|
| 1199 |
+
df = DataFrame(
|
| 1200 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1201 |
+
)
|
| 1202 |
+
# invalid assignees
|
| 1203 |
+
msg = "left hand side of an assignment must be a single name"
|
| 1204 |
+
with pytest.raises(SyntaxError, match=msg):
|
| 1205 |
+
df.eval("d,c = a + b")
|
| 1206 |
+
|
| 1207 |
+
def test_assignment_column_invalid_assign_function_call(self):
|
| 1208 |
+
df = DataFrame(
|
| 1209 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1210 |
+
)
|
| 1211 |
+
msg = "cannot assign to function call"
|
| 1212 |
+
with pytest.raises(SyntaxError, match=msg):
|
| 1213 |
+
df.eval('Timestamp("20131001") = a + b')
|
| 1214 |
+
|
| 1215 |
+
def test_assignment_single_assign_existing(self):
|
| 1216 |
+
df = DataFrame(
|
| 1217 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1218 |
+
)
|
| 1219 |
+
# single assignment - existing variable
|
| 1220 |
+
expected = df.copy()
|
| 1221 |
+
expected["a"] = expected["a"] + expected["b"]
|
| 1222 |
+
df.eval("a = a + b", inplace=True)
|
| 1223 |
+
tm.assert_frame_equal(df, expected)
|
| 1224 |
+
|
| 1225 |
+
def test_assignment_single_assign_new(self):
|
| 1226 |
+
df = DataFrame(
|
| 1227 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1228 |
+
)
|
| 1229 |
+
# single assignment - new variable
|
| 1230 |
+
expected = df.copy()
|
| 1231 |
+
expected["c"] = expected["a"] + expected["b"]
|
| 1232 |
+
df.eval("c = a + b", inplace=True)
|
| 1233 |
+
tm.assert_frame_equal(df, expected)
|
| 1234 |
+
|
| 1235 |
+
def test_assignment_single_assign_local_overlap(self):
|
| 1236 |
+
df = DataFrame(
|
| 1237 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1238 |
+
)
|
| 1239 |
+
df = df.copy()
|
| 1240 |
+
a = 1 # noqa: F841
|
| 1241 |
+
df.eval("a = 1 + b", inplace=True)
|
| 1242 |
+
|
| 1243 |
+
expected = df.copy()
|
| 1244 |
+
expected["a"] = 1 + expected["b"]
|
| 1245 |
+
tm.assert_frame_equal(df, expected)
|
| 1246 |
+
|
| 1247 |
+
def test_assignment_single_assign_name(self):
|
| 1248 |
+
df = DataFrame(
|
| 1249 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1250 |
+
)
|
| 1251 |
+
|
| 1252 |
+
a = 1 # noqa: F841
|
| 1253 |
+
old_a = df.a.copy()
|
| 1254 |
+
df.eval("a = a + b", inplace=True)
|
| 1255 |
+
result = old_a + df.b
|
| 1256 |
+
tm.assert_series_equal(result, df.a, check_names=False)
|
| 1257 |
+
assert result.name is None
|
| 1258 |
+
|
| 1259 |
+
def test_assignment_multiple_raises(self):
|
| 1260 |
+
df = DataFrame(
|
| 1261 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1262 |
+
)
|
| 1263 |
+
# multiple assignment
|
| 1264 |
+
df.eval("c = a + b", inplace=True)
|
| 1265 |
+
msg = "can only assign a single expression"
|
| 1266 |
+
with pytest.raises(SyntaxError, match=msg):
|
| 1267 |
+
df.eval("c = a = b")
|
| 1268 |
+
|
| 1269 |
+
def test_assignment_explicit(self):
|
| 1270 |
+
df = DataFrame(
|
| 1271 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1272 |
+
)
|
| 1273 |
+
# explicit targets
|
| 1274 |
+
self.eval("c = df.a + df.b", local_dict={"df": df}, target=df, inplace=True)
|
| 1275 |
+
expected = df.copy()
|
| 1276 |
+
expected["c"] = expected["a"] + expected["b"]
|
| 1277 |
+
tm.assert_frame_equal(df, expected)
|
| 1278 |
+
|
| 1279 |
+
def test_column_in(self):
|
| 1280 |
+
# GH 11235
|
| 1281 |
+
df = DataFrame({"a": [11], "b": [-32]})
|
| 1282 |
+
result = df.eval("a in [11, -32]")
|
| 1283 |
+
expected = Series([True])
|
| 1284 |
+
# TODO: 2022-01-29: Name check failed with numexpr 2.7.3 in CI
|
| 1285 |
+
# but cannot reproduce locally
|
| 1286 |
+
tm.assert_series_equal(result, expected, check_names=False)
|
| 1287 |
+
|
| 1288 |
+
@pytest.mark.xfail(reason="Unknown: Omitted test_ in name prior.")
|
| 1289 |
+
def test_assignment_not_inplace(self):
|
| 1290 |
+
# see gh-9297
|
| 1291 |
+
df = DataFrame(
|
| 1292 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
|
| 1293 |
+
)
|
| 1294 |
+
|
| 1295 |
+
actual = df.eval("c = a + b", inplace=False)
|
| 1296 |
+
assert actual is not None
|
| 1297 |
+
|
| 1298 |
+
expected = df.copy()
|
| 1299 |
+
expected["c"] = expected["a"] + expected["b"]
|
| 1300 |
+
tm.assert_frame_equal(df, expected)
|
| 1301 |
+
|
| 1302 |
+
def test_multi_line_expression(self, warn_copy_on_write):
|
| 1303 |
+
# GH 11149
|
| 1304 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1305 |
+
expected = df.copy()
|
| 1306 |
+
|
| 1307 |
+
expected["c"] = expected["a"] + expected["b"]
|
| 1308 |
+
expected["d"] = expected["c"] + expected["b"]
|
| 1309 |
+
answer = df.eval(
|
| 1310 |
+
"""
|
| 1311 |
+
c = a + b
|
| 1312 |
+
d = c + b""",
|
| 1313 |
+
inplace=True,
|
| 1314 |
+
)
|
| 1315 |
+
tm.assert_frame_equal(expected, df)
|
| 1316 |
+
assert answer is None
|
| 1317 |
+
|
| 1318 |
+
expected["a"] = expected["a"] - 1
|
| 1319 |
+
expected["e"] = expected["a"] + 2
|
| 1320 |
+
answer = df.eval(
|
| 1321 |
+
"""
|
| 1322 |
+
a = a - 1
|
| 1323 |
+
e = a + 2""",
|
| 1324 |
+
inplace=True,
|
| 1325 |
+
)
|
| 1326 |
+
tm.assert_frame_equal(expected, df)
|
| 1327 |
+
assert answer is None
|
| 1328 |
+
|
| 1329 |
+
# multi-line not valid if not all assignments
|
| 1330 |
+
msg = "Multi-line expressions are only valid if all expressions contain"
|
| 1331 |
+
with pytest.raises(ValueError, match=msg):
|
| 1332 |
+
df.eval(
|
| 1333 |
+
"""
|
| 1334 |
+
a = b + 2
|
| 1335 |
+
b - 2""",
|
| 1336 |
+
inplace=False,
|
| 1337 |
+
)
|
| 1338 |
+
|
| 1339 |
+
def test_multi_line_expression_not_inplace(self):
|
| 1340 |
+
# GH 11149
|
| 1341 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1342 |
+
expected = df.copy()
|
| 1343 |
+
|
| 1344 |
+
expected["c"] = expected["a"] + expected["b"]
|
| 1345 |
+
expected["d"] = expected["c"] + expected["b"]
|
| 1346 |
+
df = df.eval(
|
| 1347 |
+
"""
|
| 1348 |
+
c = a + b
|
| 1349 |
+
d = c + b""",
|
| 1350 |
+
inplace=False,
|
| 1351 |
+
)
|
| 1352 |
+
tm.assert_frame_equal(expected, df)
|
| 1353 |
+
|
| 1354 |
+
expected["a"] = expected["a"] - 1
|
| 1355 |
+
expected["e"] = expected["a"] + 2
|
| 1356 |
+
df = df.eval(
|
| 1357 |
+
"""
|
| 1358 |
+
a = a - 1
|
| 1359 |
+
e = a + 2""",
|
| 1360 |
+
inplace=False,
|
| 1361 |
+
)
|
| 1362 |
+
tm.assert_frame_equal(expected, df)
|
| 1363 |
+
|
| 1364 |
+
def test_multi_line_expression_local_variable(self):
|
| 1365 |
+
# GH 15342
|
| 1366 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1367 |
+
expected = df.copy()
|
| 1368 |
+
|
| 1369 |
+
local_var = 7
|
| 1370 |
+
expected["c"] = expected["a"] * local_var
|
| 1371 |
+
expected["d"] = expected["c"] + local_var
|
| 1372 |
+
answer = df.eval(
|
| 1373 |
+
"""
|
| 1374 |
+
c = a * @local_var
|
| 1375 |
+
d = c + @local_var
|
| 1376 |
+
""",
|
| 1377 |
+
inplace=True,
|
| 1378 |
+
)
|
| 1379 |
+
tm.assert_frame_equal(expected, df)
|
| 1380 |
+
assert answer is None
|
| 1381 |
+
|
| 1382 |
+
def test_multi_line_expression_callable_local_variable(self):
|
| 1383 |
+
# 26426
|
| 1384 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1385 |
+
|
| 1386 |
+
def local_func(a, b):
|
| 1387 |
+
return b
|
| 1388 |
+
|
| 1389 |
+
expected = df.copy()
|
| 1390 |
+
expected["c"] = expected["a"] * local_func(1, 7)
|
| 1391 |
+
expected["d"] = expected["c"] + local_func(1, 7)
|
| 1392 |
+
answer = df.eval(
|
| 1393 |
+
"""
|
| 1394 |
+
c = a * @local_func(1, 7)
|
| 1395 |
+
d = c + @local_func(1, 7)
|
| 1396 |
+
""",
|
| 1397 |
+
inplace=True,
|
| 1398 |
+
)
|
| 1399 |
+
tm.assert_frame_equal(expected, df)
|
| 1400 |
+
assert answer is None
|
| 1401 |
+
|
| 1402 |
+
def test_multi_line_expression_callable_local_variable_with_kwargs(self):
|
| 1403 |
+
# 26426
|
| 1404 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1405 |
+
|
| 1406 |
+
def local_func(a, b):
|
| 1407 |
+
return b
|
| 1408 |
+
|
| 1409 |
+
expected = df.copy()
|
| 1410 |
+
expected["c"] = expected["a"] * local_func(b=7, a=1)
|
| 1411 |
+
expected["d"] = expected["c"] + local_func(b=7, a=1)
|
| 1412 |
+
answer = df.eval(
|
| 1413 |
+
"""
|
| 1414 |
+
c = a * @local_func(b=7, a=1)
|
| 1415 |
+
d = c + @local_func(b=7, a=1)
|
| 1416 |
+
""",
|
| 1417 |
+
inplace=True,
|
| 1418 |
+
)
|
| 1419 |
+
tm.assert_frame_equal(expected, df)
|
| 1420 |
+
assert answer is None
|
| 1421 |
+
|
| 1422 |
+
def test_assignment_in_query(self):
|
| 1423 |
+
# GH 8664
|
| 1424 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1425 |
+
df_orig = df.copy()
|
| 1426 |
+
msg = "cannot assign without a target object"
|
| 1427 |
+
with pytest.raises(ValueError, match=msg):
|
| 1428 |
+
df.query("a = 1")
|
| 1429 |
+
tm.assert_frame_equal(df, df_orig)
|
| 1430 |
+
|
| 1431 |
+
def test_query_inplace(self):
|
| 1432 |
+
# see gh-11149
|
| 1433 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1434 |
+
expected = df.copy()
|
| 1435 |
+
expected = expected[expected["a"] == 2]
|
| 1436 |
+
df.query("a == 2", inplace=True)
|
| 1437 |
+
tm.assert_frame_equal(expected, df)
|
| 1438 |
+
|
| 1439 |
+
df = {}
|
| 1440 |
+
expected = {"a": 3}
|
| 1441 |
+
|
| 1442 |
+
self.eval("a = 1 + 2", target=df, inplace=True)
|
| 1443 |
+
tm.assert_dict_equal(df, expected)
|
| 1444 |
+
|
| 1445 |
+
@pytest.mark.parametrize("invalid_target", [1, "cat", [1, 2], np.array([]), (1, 3)])
|
| 1446 |
+
def test_cannot_item_assign(self, invalid_target):
|
| 1447 |
+
msg = "Cannot assign expression output to target"
|
| 1448 |
+
expression = "a = 1 + 2"
|
| 1449 |
+
|
| 1450 |
+
with pytest.raises(ValueError, match=msg):
|
| 1451 |
+
self.eval(expression, target=invalid_target, inplace=True)
|
| 1452 |
+
|
| 1453 |
+
if hasattr(invalid_target, "copy"):
|
| 1454 |
+
with pytest.raises(ValueError, match=msg):
|
| 1455 |
+
self.eval(expression, target=invalid_target, inplace=False)
|
| 1456 |
+
|
| 1457 |
+
@pytest.mark.parametrize("invalid_target", [1, "cat", (1, 3)])
|
| 1458 |
+
def test_cannot_copy_item(self, invalid_target):
|
| 1459 |
+
msg = "Cannot return a copy of the target"
|
| 1460 |
+
expression = "a = 1 + 2"
|
| 1461 |
+
|
| 1462 |
+
with pytest.raises(ValueError, match=msg):
|
| 1463 |
+
self.eval(expression, target=invalid_target, inplace=False)
|
| 1464 |
+
|
| 1465 |
+
@pytest.mark.parametrize("target", [1, "cat", [1, 2], np.array([]), (1, 3), {1: 2}])
|
| 1466 |
+
def test_inplace_no_assignment(self, target):
|
| 1467 |
+
expression = "1 + 2"
|
| 1468 |
+
|
| 1469 |
+
assert self.eval(expression, target=target, inplace=False) == 3
|
| 1470 |
+
|
| 1471 |
+
msg = "Cannot operate inplace if there is no assignment"
|
| 1472 |
+
with pytest.raises(ValueError, match=msg):
|
| 1473 |
+
self.eval(expression, target=target, inplace=True)
|
| 1474 |
+
|
| 1475 |
+
def test_basic_period_index_boolean_expression(self):
|
| 1476 |
+
df = DataFrame(
|
| 1477 |
+
np.random.default_rng(2).standard_normal((2, 2)),
|
| 1478 |
+
columns=period_range("2020-01-01", freq="D", periods=2),
|
| 1479 |
+
)
|
| 1480 |
+
e = df < 2
|
| 1481 |
+
r = self.eval("df < 2", local_dict={"df": df})
|
| 1482 |
+
x = df < 2
|
| 1483 |
+
|
| 1484 |
+
tm.assert_frame_equal(r, e)
|
| 1485 |
+
tm.assert_frame_equal(x, e)
|
| 1486 |
+
|
| 1487 |
+
def test_basic_period_index_subscript_expression(self):
|
| 1488 |
+
df = DataFrame(
|
| 1489 |
+
np.random.default_rng(2).standard_normal((2, 2)),
|
| 1490 |
+
columns=period_range("2020-01-01", freq="D", periods=2),
|
| 1491 |
+
)
|
| 1492 |
+
r = self.eval("df[df < 2 + 3]", local_dict={"df": df})
|
| 1493 |
+
e = df[df < 2 + 3]
|
| 1494 |
+
tm.assert_frame_equal(r, e)
|
| 1495 |
+
|
| 1496 |
+
def test_nested_period_index_subscript_expression(self):
|
| 1497 |
+
df = DataFrame(
|
| 1498 |
+
np.random.default_rng(2).standard_normal((2, 2)),
|
| 1499 |
+
columns=period_range("2020-01-01", freq="D", periods=2),
|
| 1500 |
+
)
|
| 1501 |
+
r = self.eval("df[df[df < 2] < 2] + df * 2", local_dict={"df": df})
|
| 1502 |
+
e = df[df[df < 2] < 2] + df * 2
|
| 1503 |
+
tm.assert_frame_equal(r, e)
|
| 1504 |
+
|
| 1505 |
+
def test_date_boolean(self, engine, parser):
|
| 1506 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
| 1507 |
+
df["dates1"] = date_range("1/1/2012", periods=5)
|
| 1508 |
+
res = self.eval(
|
| 1509 |
+
"df.dates1 < 20130101",
|
| 1510 |
+
local_dict={"df": df},
|
| 1511 |
+
engine=engine,
|
| 1512 |
+
parser=parser,
|
| 1513 |
+
)
|
| 1514 |
+
expec = df.dates1 < "20130101"
|
| 1515 |
+
tm.assert_series_equal(res, expec, check_names=False)
|
| 1516 |
+
|
| 1517 |
+
def test_simple_in_ops(self, engine, parser):
|
| 1518 |
+
if parser != "python":
|
| 1519 |
+
res = pd.eval("1 in [1, 2]", engine=engine, parser=parser)
|
| 1520 |
+
assert res
|
| 1521 |
+
|
| 1522 |
+
res = pd.eval("2 in (1, 2)", engine=engine, parser=parser)
|
| 1523 |
+
assert res
|
| 1524 |
+
|
| 1525 |
+
res = pd.eval("3 in (1, 2)", engine=engine, parser=parser)
|
| 1526 |
+
assert not res
|
| 1527 |
+
|
| 1528 |
+
res = pd.eval("3 not in (1, 2)", engine=engine, parser=parser)
|
| 1529 |
+
assert res
|
| 1530 |
+
|
| 1531 |
+
res = pd.eval("[3] not in (1, 2)", engine=engine, parser=parser)
|
| 1532 |
+
assert res
|
| 1533 |
+
|
| 1534 |
+
res = pd.eval("[3] in ([3], 2)", engine=engine, parser=parser)
|
| 1535 |
+
assert res
|
| 1536 |
+
|
| 1537 |
+
res = pd.eval("[[3]] in [[[3]], 2]", engine=engine, parser=parser)
|
| 1538 |
+
assert res
|
| 1539 |
+
|
| 1540 |
+
res = pd.eval("(3,) in [(3,), 2]", engine=engine, parser=parser)
|
| 1541 |
+
assert res
|
| 1542 |
+
|
| 1543 |
+
res = pd.eval("(3,) not in [(3,), 2]", engine=engine, parser=parser)
|
| 1544 |
+
assert not res
|
| 1545 |
+
|
| 1546 |
+
res = pd.eval("[(3,)] in [[(3,)], 2]", engine=engine, parser=parser)
|
| 1547 |
+
assert res
|
| 1548 |
+
else:
|
| 1549 |
+
msg = "'In' nodes are not implemented"
|
| 1550 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1551 |
+
pd.eval("1 in [1, 2]", engine=engine, parser=parser)
|
| 1552 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1553 |
+
pd.eval("2 in (1, 2)", engine=engine, parser=parser)
|
| 1554 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1555 |
+
pd.eval("3 in (1, 2)", engine=engine, parser=parser)
|
| 1556 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1557 |
+
pd.eval("[(3,)] in (1, 2, [(3,)])", engine=engine, parser=parser)
|
| 1558 |
+
msg = "'NotIn' nodes are not implemented"
|
| 1559 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1560 |
+
pd.eval("3 not in (1, 2)", engine=engine, parser=parser)
|
| 1561 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1562 |
+
pd.eval("[3] not in (1, 2, [[3]])", engine=engine, parser=parser)
|
| 1563 |
+
|
| 1564 |
+
def test_check_many_exprs(self, engine, parser):
|
| 1565 |
+
a = 1 # noqa: F841
|
| 1566 |
+
expr = " * ".join("a" * 33)
|
| 1567 |
+
expected = 1
|
| 1568 |
+
res = pd.eval(expr, engine=engine, parser=parser)
|
| 1569 |
+
assert res == expected
|
| 1570 |
+
|
| 1571 |
+
@pytest.mark.parametrize(
|
| 1572 |
+
"expr",
|
| 1573 |
+
[
|
| 1574 |
+
"df > 2 and df > 3",
|
| 1575 |
+
"df > 2 or df > 3",
|
| 1576 |
+
"not df > 2",
|
| 1577 |
+
],
|
| 1578 |
+
)
|
| 1579 |
+
def test_fails_and_or_not(self, expr, engine, parser):
|
| 1580 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
|
| 1581 |
+
if parser == "python":
|
| 1582 |
+
msg = "'BoolOp' nodes are not implemented"
|
| 1583 |
+
if "not" in expr:
|
| 1584 |
+
msg = "'Not' nodes are not implemented"
|
| 1585 |
+
|
| 1586 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1587 |
+
pd.eval(
|
| 1588 |
+
expr,
|
| 1589 |
+
local_dict={"df": df},
|
| 1590 |
+
parser=parser,
|
| 1591 |
+
engine=engine,
|
| 1592 |
+
)
|
| 1593 |
+
else:
|
| 1594 |
+
# smoke-test, should not raise
|
| 1595 |
+
pd.eval(
|
| 1596 |
+
expr,
|
| 1597 |
+
local_dict={"df": df},
|
| 1598 |
+
parser=parser,
|
| 1599 |
+
engine=engine,
|
| 1600 |
+
)
|
| 1601 |
+
|
| 1602 |
+
@pytest.mark.parametrize("char", ["|", "&"])
|
| 1603 |
+
def test_fails_ampersand_pipe(self, char, engine, parser):
|
| 1604 |
+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3))) # noqa: F841
|
| 1605 |
+
ex = f"(df + 2)[df > 1] > 0 {char} (df > 0)"
|
| 1606 |
+
if parser == "python":
|
| 1607 |
+
msg = "cannot evaluate scalar only bool ops"
|
| 1608 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1609 |
+
pd.eval(ex, parser=parser, engine=engine)
|
| 1610 |
+
else:
|
| 1611 |
+
# smoke-test, should not raise
|
| 1612 |
+
pd.eval(ex, parser=parser, engine=engine)
|
| 1613 |
+
|
| 1614 |
+
|
| 1615 |
+
class TestMath:
|
| 1616 |
+
def eval(self, *args, **kwargs):
|
| 1617 |
+
kwargs["level"] = kwargs.pop("level", 0) + 1
|
| 1618 |
+
return pd.eval(*args, **kwargs)
|
| 1619 |
+
|
| 1620 |
+
@pytest.mark.skipif(
|
| 1621 |
+
not NUMEXPR_INSTALLED, reason="Unary ops only implemented for numexpr"
|
| 1622 |
+
)
|
| 1623 |
+
@pytest.mark.parametrize("fn", _unary_math_ops)
|
| 1624 |
+
def test_unary_functions(self, fn):
|
| 1625 |
+
df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
|
| 1626 |
+
a = df.a
|
| 1627 |
+
|
| 1628 |
+
expr = f"{fn}(a)"
|
| 1629 |
+
got = self.eval(expr)
|
| 1630 |
+
with np.errstate(all="ignore"):
|
| 1631 |
+
expect = getattr(np, fn)(a)
|
| 1632 |
+
tm.assert_series_equal(got, expect, check_names=False)
|
| 1633 |
+
|
| 1634 |
+
@pytest.mark.parametrize("fn", _binary_math_ops)
|
| 1635 |
+
def test_binary_functions(self, fn):
|
| 1636 |
+
df = DataFrame(
|
| 1637 |
+
{
|
| 1638 |
+
"a": np.random.default_rng(2).standard_normal(10),
|
| 1639 |
+
"b": np.random.default_rng(2).standard_normal(10),
|
| 1640 |
+
}
|
| 1641 |
+
)
|
| 1642 |
+
a = df.a
|
| 1643 |
+
b = df.b
|
| 1644 |
+
|
| 1645 |
+
expr = f"{fn}(a, b)"
|
| 1646 |
+
got = self.eval(expr)
|
| 1647 |
+
with np.errstate(all="ignore"):
|
| 1648 |
+
expect = getattr(np, fn)(a, b)
|
| 1649 |
+
tm.assert_almost_equal(got, expect, check_names=False)
|
| 1650 |
+
|
| 1651 |
+
def test_df_use_case(self, engine, parser):
|
| 1652 |
+
df = DataFrame(
|
| 1653 |
+
{
|
| 1654 |
+
"a": np.random.default_rng(2).standard_normal(10),
|
| 1655 |
+
"b": np.random.default_rng(2).standard_normal(10),
|
| 1656 |
+
}
|
| 1657 |
+
)
|
| 1658 |
+
df.eval(
|
| 1659 |
+
"e = arctan2(sin(a), b)",
|
| 1660 |
+
engine=engine,
|
| 1661 |
+
parser=parser,
|
| 1662 |
+
inplace=True,
|
| 1663 |
+
)
|
| 1664 |
+
got = df.e
|
| 1665 |
+
expect = np.arctan2(np.sin(df.a), df.b)
|
| 1666 |
+
tm.assert_series_equal(got, expect, check_names=False)
|
| 1667 |
+
|
| 1668 |
+
def test_df_arithmetic_subexpression(self, engine, parser):
|
| 1669 |
+
df = DataFrame(
|
| 1670 |
+
{
|
| 1671 |
+
"a": np.random.default_rng(2).standard_normal(10),
|
| 1672 |
+
"b": np.random.default_rng(2).standard_normal(10),
|
| 1673 |
+
}
|
| 1674 |
+
)
|
| 1675 |
+
df.eval("e = sin(a + b)", engine=engine, parser=parser, inplace=True)
|
| 1676 |
+
got = df.e
|
| 1677 |
+
expect = np.sin(df.a + df.b)
|
| 1678 |
+
tm.assert_series_equal(got, expect, check_names=False)
|
| 1679 |
+
|
| 1680 |
+
@pytest.mark.parametrize(
|
| 1681 |
+
"dtype, expect_dtype",
|
| 1682 |
+
[
|
| 1683 |
+
(np.int32, np.float64),
|
| 1684 |
+
(np.int64, np.float64),
|
| 1685 |
+
(np.float32, np.float32),
|
| 1686 |
+
(np.float64, np.float64),
|
| 1687 |
+
pytest.param(np.complex128, np.complex128, marks=td.skip_if_windows),
|
| 1688 |
+
],
|
| 1689 |
+
)
|
| 1690 |
+
def test_result_types(self, dtype, expect_dtype, engine, parser):
|
| 1691 |
+
# xref https://github.com/pandas-dev/pandas/issues/12293
|
| 1692 |
+
# this fails on Windows, apparently a floating point precision issue
|
| 1693 |
+
|
| 1694 |
+
# Did not test complex64 because DataFrame is converting it to
|
| 1695 |
+
# complex128. Due to https://github.com/pandas-dev/pandas/issues/10952
|
| 1696 |
+
df = DataFrame(
|
| 1697 |
+
{"a": np.random.default_rng(2).standard_normal(10).astype(dtype)}
|
| 1698 |
+
)
|
| 1699 |
+
assert df.a.dtype == dtype
|
| 1700 |
+
df.eval("b = sin(a)", engine=engine, parser=parser, inplace=True)
|
| 1701 |
+
got = df.b
|
| 1702 |
+
expect = np.sin(df.a)
|
| 1703 |
+
assert expect.dtype == got.dtype
|
| 1704 |
+
assert expect_dtype == got.dtype
|
| 1705 |
+
tm.assert_series_equal(got, expect, check_names=False)
|
| 1706 |
+
|
| 1707 |
+
def test_undefined_func(self, engine, parser):
|
| 1708 |
+
df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
|
| 1709 |
+
msg = '"mysin" is not a supported function'
|
| 1710 |
+
|
| 1711 |
+
with pytest.raises(ValueError, match=msg):
|
| 1712 |
+
df.eval("mysin(a)", engine=engine, parser=parser)
|
| 1713 |
+
|
| 1714 |
+
def test_keyword_arg(self, engine, parser):
|
| 1715 |
+
df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
|
| 1716 |
+
msg = 'Function "sin" does not support keyword arguments'
|
| 1717 |
+
|
| 1718 |
+
with pytest.raises(TypeError, match=msg):
|
| 1719 |
+
df.eval("sin(x=a)", engine=engine, parser=parser)
|
| 1720 |
+
|
| 1721 |
+
|
| 1722 |
+
_var_s = np.random.default_rng(2).standard_normal(10)
|
| 1723 |
+
|
| 1724 |
+
|
| 1725 |
+
class TestScope:
|
| 1726 |
+
def test_global_scope(self, engine, parser):
|
| 1727 |
+
e = "_var_s * 2"
|
| 1728 |
+
tm.assert_numpy_array_equal(
|
| 1729 |
+
_var_s * 2, pd.eval(e, engine=engine, parser=parser)
|
| 1730 |
+
)
|
| 1731 |
+
|
| 1732 |
+
def test_no_new_locals(self, engine, parser):
|
| 1733 |
+
x = 1
|
| 1734 |
+
lcls = locals().copy()
|
| 1735 |
+
pd.eval("x + 1", local_dict=lcls, engine=engine, parser=parser)
|
| 1736 |
+
lcls2 = locals().copy()
|
| 1737 |
+
lcls2.pop("lcls")
|
| 1738 |
+
assert lcls == lcls2
|
| 1739 |
+
|
| 1740 |
+
def test_no_new_globals(self, engine, parser):
|
| 1741 |
+
x = 1 # noqa: F841
|
| 1742 |
+
gbls = globals().copy()
|
| 1743 |
+
pd.eval("x + 1", engine=engine, parser=parser)
|
| 1744 |
+
gbls2 = globals().copy()
|
| 1745 |
+
assert gbls == gbls2
|
| 1746 |
+
|
| 1747 |
+
def test_empty_locals(self, engine, parser):
|
| 1748 |
+
# GH 47084
|
| 1749 |
+
x = 1 # noqa: F841
|
| 1750 |
+
msg = "name 'x' is not defined"
|
| 1751 |
+
with pytest.raises(UndefinedVariableError, match=msg):
|
| 1752 |
+
pd.eval("x + 1", engine=engine, parser=parser, local_dict={})
|
| 1753 |
+
|
| 1754 |
+
def test_empty_globals(self, engine, parser):
|
| 1755 |
+
# GH 47084
|
| 1756 |
+
msg = "name '_var_s' is not defined"
|
| 1757 |
+
e = "_var_s * 2"
|
| 1758 |
+
with pytest.raises(UndefinedVariableError, match=msg):
|
| 1759 |
+
pd.eval(e, engine=engine, parser=parser, global_dict={})
|
| 1760 |
+
|
| 1761 |
+
|
| 1762 |
+
@td.skip_if_no("numexpr")
|
| 1763 |
+
def test_invalid_engine():
|
| 1764 |
+
msg = "Invalid engine 'asdf' passed"
|
| 1765 |
+
with pytest.raises(KeyError, match=msg):
|
| 1766 |
+
pd.eval("x + y", local_dict={"x": 1, "y": 2}, engine="asdf")
|
| 1767 |
+
|
| 1768 |
+
|
| 1769 |
+
@td.skip_if_no("numexpr")
|
| 1770 |
+
@pytest.mark.parametrize(
|
| 1771 |
+
("use_numexpr", "expected"),
|
| 1772 |
+
(
|
| 1773 |
+
(True, "numexpr"),
|
| 1774 |
+
(False, "python"),
|
| 1775 |
+
),
|
| 1776 |
+
)
|
| 1777 |
+
def test_numexpr_option_respected(use_numexpr, expected):
|
| 1778 |
+
# GH 32556
|
| 1779 |
+
from pandas.core.computation.eval import _check_engine
|
| 1780 |
+
|
| 1781 |
+
with pd.option_context("compute.use_numexpr", use_numexpr):
|
| 1782 |
+
result = _check_engine(None)
|
| 1783 |
+
assert result == expected
|
| 1784 |
+
|
| 1785 |
+
|
| 1786 |
+
@td.skip_if_no("numexpr")
|
| 1787 |
+
def test_numexpr_option_incompatible_op():
|
| 1788 |
+
# GH 32556
|
| 1789 |
+
with pd.option_context("compute.use_numexpr", False):
|
| 1790 |
+
df = DataFrame(
|
| 1791 |
+
{"A": [True, False, True, False, None, None], "B": [1, 2, 3, 4, 5, 6]}
|
| 1792 |
+
)
|
| 1793 |
+
result = df.query("A.isnull()")
|
| 1794 |
+
expected = DataFrame({"A": [None, None], "B": [5, 6]}, index=[4, 5])
|
| 1795 |
+
tm.assert_frame_equal(result, expected)
|
| 1796 |
+
|
| 1797 |
+
|
| 1798 |
+
@td.skip_if_no("numexpr")
|
| 1799 |
+
def test_invalid_parser():
|
| 1800 |
+
msg = "Invalid parser 'asdf' passed"
|
| 1801 |
+
with pytest.raises(KeyError, match=msg):
|
| 1802 |
+
pd.eval("x + y", local_dict={"x": 1, "y": 2}, parser="asdf")
|
| 1803 |
+
|
| 1804 |
+
|
| 1805 |
+
_parsers: dict[str, type[BaseExprVisitor]] = {
|
| 1806 |
+
"python": PythonExprVisitor,
|
| 1807 |
+
"pytables": pytables.PyTablesExprVisitor,
|
| 1808 |
+
"pandas": PandasExprVisitor,
|
| 1809 |
+
}
|
| 1810 |
+
|
| 1811 |
+
|
| 1812 |
+
@pytest.mark.parametrize("engine", ENGINES)
|
| 1813 |
+
@pytest.mark.parametrize("parser", _parsers)
|
| 1814 |
+
def test_disallowed_nodes(engine, parser):
|
| 1815 |
+
VisitorClass = _parsers[parser]
|
| 1816 |
+
inst = VisitorClass("x + 1", engine, parser)
|
| 1817 |
+
|
| 1818 |
+
for ops in VisitorClass.unsupported_nodes:
|
| 1819 |
+
msg = "nodes are not implemented"
|
| 1820 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1821 |
+
getattr(inst, ops)()
|
| 1822 |
+
|
| 1823 |
+
|
| 1824 |
+
def test_syntax_error_exprs(engine, parser):
|
| 1825 |
+
e = "s +"
|
| 1826 |
+
with pytest.raises(SyntaxError, match="invalid syntax"):
|
| 1827 |
+
pd.eval(e, engine=engine, parser=parser)
|
| 1828 |
+
|
| 1829 |
+
|
| 1830 |
+
def test_name_error_exprs(engine, parser):
|
| 1831 |
+
e = "s + t"
|
| 1832 |
+
msg = "name 's' is not defined"
|
| 1833 |
+
with pytest.raises(NameError, match=msg):
|
| 1834 |
+
pd.eval(e, engine=engine, parser=parser)
|
| 1835 |
+
|
| 1836 |
+
|
| 1837 |
+
@pytest.mark.parametrize("express", ["a + @b", "@a + b", "@a + @b"])
|
| 1838 |
+
def test_invalid_local_variable_reference(engine, parser, express):
|
| 1839 |
+
a, b = 1, 2 # noqa: F841
|
| 1840 |
+
|
| 1841 |
+
if parser != "pandas":
|
| 1842 |
+
with pytest.raises(SyntaxError, match="The '@' prefix is only"):
|
| 1843 |
+
pd.eval(express, engine=engine, parser=parser)
|
| 1844 |
+
else:
|
| 1845 |
+
with pytest.raises(SyntaxError, match="The '@' prefix is not"):
|
| 1846 |
+
pd.eval(express, engine=engine, parser=parser)
|
| 1847 |
+
|
| 1848 |
+
|
| 1849 |
+
def test_numexpr_builtin_raises(engine, parser):
|
| 1850 |
+
sin, dotted_line = 1, 2
|
| 1851 |
+
if engine == "numexpr":
|
| 1852 |
+
msg = "Variables in expression .+"
|
| 1853 |
+
with pytest.raises(NumExprClobberingError, match=msg):
|
| 1854 |
+
pd.eval("sin + dotted_line", engine=engine, parser=parser)
|
| 1855 |
+
else:
|
| 1856 |
+
res = pd.eval("sin + dotted_line", engine=engine, parser=parser)
|
| 1857 |
+
assert res == sin + dotted_line
|
| 1858 |
+
|
| 1859 |
+
|
| 1860 |
+
def test_bad_resolver_raises(engine, parser):
|
| 1861 |
+
cannot_resolve = 42, 3.0
|
| 1862 |
+
with pytest.raises(TypeError, match="Resolver of type .+"):
|
| 1863 |
+
pd.eval("1 + 2", resolvers=cannot_resolve, engine=engine, parser=parser)
|
| 1864 |
+
|
| 1865 |
+
|
| 1866 |
+
def test_empty_string_raises(engine, parser):
|
| 1867 |
+
# GH 13139
|
| 1868 |
+
with pytest.raises(ValueError, match="expr cannot be an empty string"):
|
| 1869 |
+
pd.eval("", engine=engine, parser=parser)
|
| 1870 |
+
|
| 1871 |
+
|
| 1872 |
+
def test_more_than_one_expression_raises(engine, parser):
|
| 1873 |
+
with pytest.raises(SyntaxError, match="only a single expression is allowed"):
|
| 1874 |
+
pd.eval("1 + 1; 2 + 2", engine=engine, parser=parser)
|
| 1875 |
+
|
| 1876 |
+
|
| 1877 |
+
@pytest.mark.parametrize("cmp", ("and", "or"))
|
| 1878 |
+
@pytest.mark.parametrize("lhs", (int, float))
|
| 1879 |
+
@pytest.mark.parametrize("rhs", (int, float))
|
| 1880 |
+
def test_bool_ops_fails_on_scalars(lhs, cmp, rhs, engine, parser):
|
| 1881 |
+
gen = {
|
| 1882 |
+
int: lambda: np.random.default_rng(2).integers(10),
|
| 1883 |
+
float: np.random.default_rng(2).standard_normal,
|
| 1884 |
+
}
|
| 1885 |
+
|
| 1886 |
+
mid = gen[lhs]() # noqa: F841
|
| 1887 |
+
lhs = gen[lhs]()
|
| 1888 |
+
rhs = gen[rhs]()
|
| 1889 |
+
|
| 1890 |
+
ex1 = f"lhs {cmp} mid {cmp} rhs"
|
| 1891 |
+
ex2 = f"lhs {cmp} mid and mid {cmp} rhs"
|
| 1892 |
+
ex3 = f"(lhs {cmp} mid) & (mid {cmp} rhs)"
|
| 1893 |
+
for ex in (ex1, ex2, ex3):
|
| 1894 |
+
msg = "cannot evaluate scalar only bool ops|'BoolOp' nodes are not"
|
| 1895 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1896 |
+
pd.eval(ex, engine=engine, parser=parser)
|
| 1897 |
+
|
| 1898 |
+
|
| 1899 |
+
@pytest.mark.parametrize(
|
| 1900 |
+
"other",
|
| 1901 |
+
[
|
| 1902 |
+
"'x'",
|
| 1903 |
+
"...",
|
| 1904 |
+
],
|
| 1905 |
+
)
|
| 1906 |
+
def test_equals_various(other):
|
| 1907 |
+
df = DataFrame({"A": ["a", "b", "c"]}, dtype=object)
|
| 1908 |
+
result = df.eval(f"A == {other}")
|
| 1909 |
+
expected = Series([False, False, False], name="A")
|
| 1910 |
+
if USE_NUMEXPR:
|
| 1911 |
+
# https://github.com/pandas-dev/pandas/issues/10239
|
| 1912 |
+
# lose name with numexpr engine. Remove when that's fixed.
|
| 1913 |
+
expected.name = None
|
| 1914 |
+
tm.assert_series_equal(result, expected)
|
| 1915 |
+
|
| 1916 |
+
|
| 1917 |
+
def test_inf(engine, parser):
|
| 1918 |
+
s = "inf + 1"
|
| 1919 |
+
expected = np.inf
|
| 1920 |
+
result = pd.eval(s, engine=engine, parser=parser)
|
| 1921 |
+
assert result == expected
|
| 1922 |
+
|
| 1923 |
+
|
| 1924 |
+
@pytest.mark.parametrize("column", ["Temp(°C)", "Capacitance(μF)"])
|
| 1925 |
+
def test_query_token(engine, column):
|
| 1926 |
+
# See: https://github.com/pandas-dev/pandas/pull/42826
|
| 1927 |
+
df = DataFrame(
|
| 1928 |
+
np.random.default_rng(2).standard_normal((5, 2)), columns=[column, "b"]
|
| 1929 |
+
)
|
| 1930 |
+
expected = df[df[column] > 5]
|
| 1931 |
+
query_string = f"`{column}` > 5"
|
| 1932 |
+
result = df.query(query_string, engine=engine)
|
| 1933 |
+
tm.assert_frame_equal(result, expected)
|
| 1934 |
+
|
| 1935 |
+
|
| 1936 |
+
def test_negate_lt_eq_le(engine, parser):
|
| 1937 |
+
df = DataFrame([[0, 10], [1, 20]], columns=["cat", "count"])
|
| 1938 |
+
expected = df[~(df.cat > 0)]
|
| 1939 |
+
|
| 1940 |
+
result = df.query("~(cat > 0)", engine=engine, parser=parser)
|
| 1941 |
+
tm.assert_frame_equal(result, expected)
|
| 1942 |
+
|
| 1943 |
+
if parser == "python":
|
| 1944 |
+
msg = "'Not' nodes are not implemented"
|
| 1945 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 1946 |
+
df.query("not (cat > 0)", engine=engine, parser=parser)
|
| 1947 |
+
else:
|
| 1948 |
+
result = df.query("not (cat > 0)", engine=engine, parser=parser)
|
| 1949 |
+
tm.assert_frame_equal(result, expected)
|
| 1950 |
+
|
| 1951 |
+
|
| 1952 |
+
@pytest.mark.parametrize(
|
| 1953 |
+
"column",
|
| 1954 |
+
DEFAULT_GLOBALS.keys(),
|
| 1955 |
+
)
|
| 1956 |
+
def test_eval_no_support_column_name(request, column):
|
| 1957 |
+
# GH 44603
|
| 1958 |
+
if column in ["True", "False", "inf", "Inf"]:
|
| 1959 |
+
request.applymarker(
|
| 1960 |
+
pytest.mark.xfail(
|
| 1961 |
+
raises=KeyError,
|
| 1962 |
+
reason=f"GH 47859 DataFrame eval not supported with {column}",
|
| 1963 |
+
)
|
| 1964 |
+
)
|
| 1965 |
+
|
| 1966 |
+
df = DataFrame(
|
| 1967 |
+
np.random.default_rng(2).integers(0, 100, size=(10, 2)),
|
| 1968 |
+
columns=[column, "col1"],
|
| 1969 |
+
)
|
| 1970 |
+
expected = df[df[column] > 6]
|
| 1971 |
+
result = df.query(f"{column}>6")
|
| 1972 |
+
|
| 1973 |
+
tm.assert_frame_equal(result, expected)
|
| 1974 |
+
|
| 1975 |
+
|
| 1976 |
+
def test_set_inplace(using_copy_on_write, warn_copy_on_write):
|
| 1977 |
+
# https://github.com/pandas-dev/pandas/issues/47449
|
| 1978 |
+
# Ensure we don't only update the DataFrame inplace, but also the actual
|
| 1979 |
+
# column values, such that references to this column also get updated
|
| 1980 |
+
df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
|
| 1981 |
+
result_view = df[:]
|
| 1982 |
+
ser = df["A"]
|
| 1983 |
+
with tm.assert_cow_warning(warn_copy_on_write):
|
| 1984 |
+
df.eval("A = B + C", inplace=True)
|
| 1985 |
+
expected = DataFrame({"A": [11, 13, 15], "B": [4, 5, 6], "C": [7, 8, 9]})
|
| 1986 |
+
tm.assert_frame_equal(df, expected)
|
| 1987 |
+
if not using_copy_on_write:
|
| 1988 |
+
tm.assert_series_equal(ser, expected["A"])
|
| 1989 |
+
tm.assert_series_equal(result_view["A"], expected["A"])
|
| 1990 |
+
else:
|
| 1991 |
+
expected = Series([1, 2, 3], name="A")
|
| 1992 |
+
tm.assert_series_equal(ser, expected)
|
| 1993 |
+
tm.assert_series_equal(result_view["A"], expected)
|
| 1994 |
+
|
| 1995 |
+
|
| 1996 |
+
class TestValidate:
|
| 1997 |
+
@pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0])
|
| 1998 |
+
def test_validate_bool_args(self, value):
|
| 1999 |
+
msg = 'For argument "inplace" expected type bool, received type'
|
| 2000 |
+
with pytest.raises(ValueError, match=msg):
|
| 2001 |
+
pd.eval("2+2", inplace=value)
|
lib/python3.12/site-packages/pandas/tests/indexes/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (211 Bytes). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_append.cpython-312.pyc
ADDED
|
Binary file (4.22 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_astype.cpython-312.pyc
ADDED
|
Binary file (4.36 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_category.cpython-312.pyc
ADDED
|
Binary file (22 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_constructors.cpython-312.pyc
ADDED
|
Binary file (8.06 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_equals.cpython-312.pyc
ADDED
|
Binary file (6.18 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_fillna.cpython-312.pyc
ADDED
|
Binary file (2.95 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_formats.cpython-312.pyc
ADDED
|
Binary file (6.65 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_indexing.cpython-312.pyc
ADDED
|
Binary file (24.7 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_map.cpython-312.pyc
ADDED
|
Binary file (7.45 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_reindex.cpython-312.pyc
ADDED
|
Binary file (4.95 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/__pycache__/test_setops.cpython-312.pyc
ADDED
|
Binary file (1.02 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_append.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas import (
|
| 4 |
+
CategoricalIndex,
|
| 5 |
+
Index,
|
| 6 |
+
)
|
| 7 |
+
import pandas._testing as tm
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TestAppend:
|
| 11 |
+
@pytest.fixture
|
| 12 |
+
def ci(self):
|
| 13 |
+
categories = list("cab")
|
| 14 |
+
return CategoricalIndex(list("aabbca"), categories=categories, ordered=False)
|
| 15 |
+
|
| 16 |
+
def test_append(self, ci):
|
| 17 |
+
# append cats with the same categories
|
| 18 |
+
result = ci[:3].append(ci[3:])
|
| 19 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 20 |
+
|
| 21 |
+
foos = [ci[:1], ci[1:3], ci[3:]]
|
| 22 |
+
result = foos[0].append(foos[1:])
|
| 23 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 24 |
+
|
| 25 |
+
def test_append_empty(self, ci):
|
| 26 |
+
# empty
|
| 27 |
+
result = ci.append([])
|
| 28 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 29 |
+
|
| 30 |
+
def test_append_mismatched_categories(self, ci):
|
| 31 |
+
# appending with different categories or reordered is not ok
|
| 32 |
+
msg = "all inputs must be Index"
|
| 33 |
+
with pytest.raises(TypeError, match=msg):
|
| 34 |
+
ci.append(ci.values.set_categories(list("abcd")))
|
| 35 |
+
with pytest.raises(TypeError, match=msg):
|
| 36 |
+
ci.append(ci.values.reorder_categories(list("abc")))
|
| 37 |
+
|
| 38 |
+
def test_append_category_objects(self, ci):
|
| 39 |
+
# with objects
|
| 40 |
+
result = ci.append(Index(["c", "a"]))
|
| 41 |
+
expected = CategoricalIndex(list("aabbcaca"), categories=ci.categories)
|
| 42 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 43 |
+
|
| 44 |
+
def test_append_non_categories(self, ci):
|
| 45 |
+
# invalid objects -> cast to object via concat_compat
|
| 46 |
+
result = ci.append(Index(["a", "d"]))
|
| 47 |
+
expected = Index(["a", "a", "b", "b", "c", "a", "a", "d"])
|
| 48 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 49 |
+
|
| 50 |
+
def test_append_object(self, ci):
|
| 51 |
+
# GH#14298 - if base object is not categorical -> coerce to object
|
| 52 |
+
result = Index(["c", "a"]).append(ci)
|
| 53 |
+
expected = Index(list("caaabbca"))
|
| 54 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 55 |
+
|
| 56 |
+
def test_append_to_another(self):
|
| 57 |
+
# hits Index._concat
|
| 58 |
+
fst = Index(["a", "b"])
|
| 59 |
+
snd = CategoricalIndex(["d", "e"])
|
| 60 |
+
result = fst.append(snd)
|
| 61 |
+
expected = Index(["a", "b", "d", "e"])
|
| 62 |
+
tm.assert_index_equal(result, expected)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_astype.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import date
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from pandas import (
|
| 7 |
+
Categorical,
|
| 8 |
+
CategoricalDtype,
|
| 9 |
+
CategoricalIndex,
|
| 10 |
+
Index,
|
| 11 |
+
IntervalIndex,
|
| 12 |
+
)
|
| 13 |
+
import pandas._testing as tm
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestAstype:
|
| 17 |
+
def test_astype(self):
|
| 18 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
|
| 19 |
+
|
| 20 |
+
result = ci.astype(object)
|
| 21 |
+
tm.assert_index_equal(result, Index(np.array(ci), dtype=object))
|
| 22 |
+
|
| 23 |
+
# this IS equal, but not the same class
|
| 24 |
+
assert result.equals(ci)
|
| 25 |
+
assert isinstance(result, Index)
|
| 26 |
+
assert not isinstance(result, CategoricalIndex)
|
| 27 |
+
|
| 28 |
+
# interval
|
| 29 |
+
ii = IntervalIndex.from_arrays(left=[-0.001, 2.0], right=[2, 4], closed="right")
|
| 30 |
+
|
| 31 |
+
ci = CategoricalIndex(
|
| 32 |
+
Categorical.from_codes([0, 1, -1], categories=ii, ordered=True)
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
result = ci.astype("interval")
|
| 36 |
+
expected = ii.take([0, 1, -1], allow_fill=True, fill_value=np.nan)
|
| 37 |
+
tm.assert_index_equal(result, expected)
|
| 38 |
+
|
| 39 |
+
result = IntervalIndex(result.values)
|
| 40 |
+
tm.assert_index_equal(result, expected)
|
| 41 |
+
|
| 42 |
+
@pytest.mark.parametrize("name", [None, "foo"])
|
| 43 |
+
@pytest.mark.parametrize("dtype_ordered", [True, False])
|
| 44 |
+
@pytest.mark.parametrize("index_ordered", [True, False])
|
| 45 |
+
def test_astype_category(self, name, dtype_ordered, index_ordered):
|
| 46 |
+
# GH#18630
|
| 47 |
+
index = CategoricalIndex(
|
| 48 |
+
list("aabbca"), categories=list("cab"), ordered=index_ordered
|
| 49 |
+
)
|
| 50 |
+
if name:
|
| 51 |
+
index = index.rename(name)
|
| 52 |
+
|
| 53 |
+
# standard categories
|
| 54 |
+
dtype = CategoricalDtype(ordered=dtype_ordered)
|
| 55 |
+
result = index.astype(dtype)
|
| 56 |
+
expected = CategoricalIndex(
|
| 57 |
+
index.tolist(),
|
| 58 |
+
name=name,
|
| 59 |
+
categories=index.categories,
|
| 60 |
+
ordered=dtype_ordered,
|
| 61 |
+
)
|
| 62 |
+
tm.assert_index_equal(result, expected)
|
| 63 |
+
|
| 64 |
+
# non-standard categories
|
| 65 |
+
dtype = CategoricalDtype(index.unique().tolist()[:-1], dtype_ordered)
|
| 66 |
+
result = index.astype(dtype)
|
| 67 |
+
expected = CategoricalIndex(index.tolist(), name=name, dtype=dtype)
|
| 68 |
+
tm.assert_index_equal(result, expected)
|
| 69 |
+
|
| 70 |
+
if dtype_ordered is False:
|
| 71 |
+
# dtype='category' can't specify ordered, so only test once
|
| 72 |
+
result = index.astype("category")
|
| 73 |
+
expected = index
|
| 74 |
+
tm.assert_index_equal(result, expected)
|
| 75 |
+
|
| 76 |
+
@pytest.mark.parametrize("box", [True, False])
|
| 77 |
+
def test_categorical_date_roundtrip(self, box):
|
| 78 |
+
# astype to categorical and back should preserve date objects
|
| 79 |
+
v = date.today()
|
| 80 |
+
|
| 81 |
+
obj = Index([v, v])
|
| 82 |
+
assert obj.dtype == object
|
| 83 |
+
if box:
|
| 84 |
+
obj = obj.array
|
| 85 |
+
|
| 86 |
+
cat = obj.astype("category")
|
| 87 |
+
|
| 88 |
+
rtrip = cat.astype(object)
|
| 89 |
+
assert rtrip.dtype == object
|
| 90 |
+
assert type(rtrip[0]) is date
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_category.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas._config import using_pyarrow_string_dtype
|
| 5 |
+
|
| 6 |
+
from pandas._libs import index as libindex
|
| 7 |
+
from pandas._libs.arrays import NDArrayBacked
|
| 8 |
+
|
| 9 |
+
import pandas as pd
|
| 10 |
+
from pandas import (
|
| 11 |
+
Categorical,
|
| 12 |
+
CategoricalDtype,
|
| 13 |
+
)
|
| 14 |
+
import pandas._testing as tm
|
| 15 |
+
from pandas.core.indexes.api import (
|
| 16 |
+
CategoricalIndex,
|
| 17 |
+
Index,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class TestCategoricalIndex:
|
| 22 |
+
@pytest.fixture
|
| 23 |
+
def simple_index(self) -> CategoricalIndex:
|
| 24 |
+
return CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
|
| 25 |
+
|
| 26 |
+
def test_can_hold_identifiers(self):
|
| 27 |
+
idx = CategoricalIndex(list("aabbca"), categories=None, ordered=False)
|
| 28 |
+
key = idx[0]
|
| 29 |
+
assert idx._can_hold_identifiers_and_holds_name(key) is True
|
| 30 |
+
|
| 31 |
+
def test_insert(self, simple_index):
|
| 32 |
+
ci = simple_index
|
| 33 |
+
categories = ci.categories
|
| 34 |
+
|
| 35 |
+
# test 0th element
|
| 36 |
+
result = ci.insert(0, "a")
|
| 37 |
+
expected = CategoricalIndex(list("aaabbca"), categories=categories)
|
| 38 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 39 |
+
|
| 40 |
+
# test Nth element that follows Python list behavior
|
| 41 |
+
result = ci.insert(-1, "a")
|
| 42 |
+
expected = CategoricalIndex(list("aabbcaa"), categories=categories)
|
| 43 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 44 |
+
|
| 45 |
+
# test empty
|
| 46 |
+
result = CategoricalIndex([], categories=categories).insert(0, "a")
|
| 47 |
+
expected = CategoricalIndex(["a"], categories=categories)
|
| 48 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 49 |
+
|
| 50 |
+
# invalid -> cast to object
|
| 51 |
+
expected = ci.astype(object).insert(0, "d")
|
| 52 |
+
result = ci.insert(0, "d").astype(object)
|
| 53 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 54 |
+
|
| 55 |
+
# GH 18295 (test missing)
|
| 56 |
+
expected = CategoricalIndex(["a", np.nan, "a", "b", "c", "b"])
|
| 57 |
+
for na in (np.nan, pd.NaT, None):
|
| 58 |
+
result = CategoricalIndex(list("aabcb")).insert(1, na)
|
| 59 |
+
tm.assert_index_equal(result, expected)
|
| 60 |
+
|
| 61 |
+
def test_insert_na_mismatched_dtype(self):
|
| 62 |
+
ci = CategoricalIndex([0, 1, 1])
|
| 63 |
+
result = ci.insert(0, pd.NaT)
|
| 64 |
+
expected = Index([pd.NaT, 0, 1, 1], dtype=object)
|
| 65 |
+
tm.assert_index_equal(result, expected)
|
| 66 |
+
|
| 67 |
+
def test_delete(self, simple_index):
|
| 68 |
+
ci = simple_index
|
| 69 |
+
categories = ci.categories
|
| 70 |
+
|
| 71 |
+
result = ci.delete(0)
|
| 72 |
+
expected = CategoricalIndex(list("abbca"), categories=categories)
|
| 73 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 74 |
+
|
| 75 |
+
result = ci.delete(-1)
|
| 76 |
+
expected = CategoricalIndex(list("aabbc"), categories=categories)
|
| 77 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 78 |
+
|
| 79 |
+
with tm.external_error_raised((IndexError, ValueError)):
|
| 80 |
+
# Either depending on NumPy version
|
| 81 |
+
ci.delete(10)
|
| 82 |
+
|
| 83 |
+
@pytest.mark.parametrize(
|
| 84 |
+
"data, non_lexsorted_data",
|
| 85 |
+
[[[1, 2, 3], [9, 0, 1, 2, 3]], [list("abc"), list("fabcd")]],
|
| 86 |
+
)
|
| 87 |
+
def test_is_monotonic(self, data, non_lexsorted_data):
|
| 88 |
+
c = CategoricalIndex(data)
|
| 89 |
+
assert c.is_monotonic_increasing is True
|
| 90 |
+
assert c.is_monotonic_decreasing is False
|
| 91 |
+
|
| 92 |
+
c = CategoricalIndex(data, ordered=True)
|
| 93 |
+
assert c.is_monotonic_increasing is True
|
| 94 |
+
assert c.is_monotonic_decreasing is False
|
| 95 |
+
|
| 96 |
+
c = CategoricalIndex(data, categories=reversed(data))
|
| 97 |
+
assert c.is_monotonic_increasing is False
|
| 98 |
+
assert c.is_monotonic_decreasing is True
|
| 99 |
+
|
| 100 |
+
c = CategoricalIndex(data, categories=reversed(data), ordered=True)
|
| 101 |
+
assert c.is_monotonic_increasing is False
|
| 102 |
+
assert c.is_monotonic_decreasing is True
|
| 103 |
+
|
| 104 |
+
# test when data is neither monotonic increasing nor decreasing
|
| 105 |
+
reordered_data = [data[0], data[2], data[1]]
|
| 106 |
+
c = CategoricalIndex(reordered_data, categories=reversed(data))
|
| 107 |
+
assert c.is_monotonic_increasing is False
|
| 108 |
+
assert c.is_monotonic_decreasing is False
|
| 109 |
+
|
| 110 |
+
# non lexsorted categories
|
| 111 |
+
categories = non_lexsorted_data
|
| 112 |
+
|
| 113 |
+
c = CategoricalIndex(categories[:2], categories=categories)
|
| 114 |
+
assert c.is_monotonic_increasing is True
|
| 115 |
+
assert c.is_monotonic_decreasing is False
|
| 116 |
+
|
| 117 |
+
c = CategoricalIndex(categories[1:3], categories=categories)
|
| 118 |
+
assert c.is_monotonic_increasing is True
|
| 119 |
+
assert c.is_monotonic_decreasing is False
|
| 120 |
+
|
| 121 |
+
def test_has_duplicates(self):
|
| 122 |
+
idx = CategoricalIndex([0, 0, 0], name="foo")
|
| 123 |
+
assert idx.is_unique is False
|
| 124 |
+
assert idx.has_duplicates is True
|
| 125 |
+
|
| 126 |
+
idx = CategoricalIndex([0, 1], categories=[2, 3], name="foo")
|
| 127 |
+
assert idx.is_unique is False
|
| 128 |
+
assert idx.has_duplicates is True
|
| 129 |
+
|
| 130 |
+
idx = CategoricalIndex([0, 1, 2, 3], categories=[1, 2, 3], name="foo")
|
| 131 |
+
assert idx.is_unique is True
|
| 132 |
+
assert idx.has_duplicates is False
|
| 133 |
+
|
| 134 |
+
@pytest.mark.parametrize(
|
| 135 |
+
"data, categories, expected",
|
| 136 |
+
[
|
| 137 |
+
(
|
| 138 |
+
[1, 1, 1],
|
| 139 |
+
[1, 2, 3],
|
| 140 |
+
{
|
| 141 |
+
"first": np.array([False, True, True]),
|
| 142 |
+
"last": np.array([True, True, False]),
|
| 143 |
+
False: np.array([True, True, True]),
|
| 144 |
+
},
|
| 145 |
+
),
|
| 146 |
+
(
|
| 147 |
+
[1, 1, 1],
|
| 148 |
+
list("abc"),
|
| 149 |
+
{
|
| 150 |
+
"first": np.array([False, True, True]),
|
| 151 |
+
"last": np.array([True, True, False]),
|
| 152 |
+
False: np.array([True, True, True]),
|
| 153 |
+
},
|
| 154 |
+
),
|
| 155 |
+
(
|
| 156 |
+
[2, "a", "b"],
|
| 157 |
+
list("abc"),
|
| 158 |
+
{
|
| 159 |
+
"first": np.zeros(shape=(3), dtype=np.bool_),
|
| 160 |
+
"last": np.zeros(shape=(3), dtype=np.bool_),
|
| 161 |
+
False: np.zeros(shape=(3), dtype=np.bool_),
|
| 162 |
+
},
|
| 163 |
+
),
|
| 164 |
+
(
|
| 165 |
+
list("abb"),
|
| 166 |
+
list("abc"),
|
| 167 |
+
{
|
| 168 |
+
"first": np.array([False, False, True]),
|
| 169 |
+
"last": np.array([False, True, False]),
|
| 170 |
+
False: np.array([False, True, True]),
|
| 171 |
+
},
|
| 172 |
+
),
|
| 173 |
+
],
|
| 174 |
+
)
|
| 175 |
+
def test_drop_duplicates(self, data, categories, expected):
|
| 176 |
+
idx = CategoricalIndex(data, categories=categories, name="foo")
|
| 177 |
+
for keep, e in expected.items():
|
| 178 |
+
tm.assert_numpy_array_equal(idx.duplicated(keep=keep), e)
|
| 179 |
+
e = idx[~e]
|
| 180 |
+
result = idx.drop_duplicates(keep=keep)
|
| 181 |
+
tm.assert_index_equal(result, e)
|
| 182 |
+
|
| 183 |
+
@pytest.mark.parametrize(
|
| 184 |
+
"data, categories, expected_data",
|
| 185 |
+
[
|
| 186 |
+
([1, 1, 1], [1, 2, 3], [1]),
|
| 187 |
+
([1, 1, 1], list("abc"), [np.nan]),
|
| 188 |
+
([1, 2, "a"], [1, 2, 3], [1, 2, np.nan]),
|
| 189 |
+
([2, "a", "b"], list("abc"), [np.nan, "a", "b"]),
|
| 190 |
+
],
|
| 191 |
+
)
|
| 192 |
+
def test_unique(self, data, categories, expected_data, ordered):
|
| 193 |
+
dtype = CategoricalDtype(categories, ordered=ordered)
|
| 194 |
+
|
| 195 |
+
idx = CategoricalIndex(data, dtype=dtype)
|
| 196 |
+
expected = CategoricalIndex(expected_data, dtype=dtype)
|
| 197 |
+
tm.assert_index_equal(idx.unique(), expected)
|
| 198 |
+
|
| 199 |
+
@pytest.mark.xfail(using_pyarrow_string_dtype(), reason="repr doesn't roundtrip")
|
| 200 |
+
def test_repr_roundtrip(self):
|
| 201 |
+
ci = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
|
| 202 |
+
str(ci)
|
| 203 |
+
tm.assert_index_equal(eval(repr(ci)), ci, exact=True)
|
| 204 |
+
|
| 205 |
+
# formatting
|
| 206 |
+
str(ci)
|
| 207 |
+
|
| 208 |
+
# long format
|
| 209 |
+
# this is not reprable
|
| 210 |
+
ci = CategoricalIndex(np.random.default_rng(2).integers(0, 5, size=100))
|
| 211 |
+
str(ci)
|
| 212 |
+
|
| 213 |
+
def test_isin(self):
|
| 214 |
+
ci = CategoricalIndex(list("aabca") + [np.nan], categories=["c", "a", "b"])
|
| 215 |
+
tm.assert_numpy_array_equal(
|
| 216 |
+
ci.isin(["c"]), np.array([False, False, False, True, False, False])
|
| 217 |
+
)
|
| 218 |
+
tm.assert_numpy_array_equal(
|
| 219 |
+
ci.isin(["c", "a", "b"]), np.array([True] * 5 + [False])
|
| 220 |
+
)
|
| 221 |
+
tm.assert_numpy_array_equal(
|
| 222 |
+
ci.isin(["c", "a", "b", np.nan]), np.array([True] * 6)
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
# mismatched categorical -> coerced to ndarray so doesn't matter
|
| 226 |
+
result = ci.isin(ci.set_categories(list("abcdefghi")))
|
| 227 |
+
expected = np.array([True] * 6)
|
| 228 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 229 |
+
|
| 230 |
+
result = ci.isin(ci.set_categories(list("defghi")))
|
| 231 |
+
expected = np.array([False] * 5 + [True])
|
| 232 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 233 |
+
|
| 234 |
+
def test_isin_overlapping_intervals(self):
|
| 235 |
+
# GH 34974
|
| 236 |
+
idx = pd.IntervalIndex([pd.Interval(0, 2), pd.Interval(0, 1)])
|
| 237 |
+
result = CategoricalIndex(idx).isin(idx)
|
| 238 |
+
expected = np.array([True, True])
|
| 239 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 240 |
+
|
| 241 |
+
def test_identical(self):
|
| 242 |
+
ci1 = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
|
| 243 |
+
ci2 = CategoricalIndex(["a", "b"], categories=["a", "b", "c"], ordered=True)
|
| 244 |
+
assert ci1.identical(ci1)
|
| 245 |
+
assert ci1.identical(ci1.copy())
|
| 246 |
+
assert not ci1.identical(ci2)
|
| 247 |
+
|
| 248 |
+
def test_ensure_copied_data(self):
|
| 249 |
+
# gh-12309: Check the "copy" argument of each
|
| 250 |
+
# Index.__new__ is honored.
|
| 251 |
+
#
|
| 252 |
+
# Must be tested separately from other indexes because
|
| 253 |
+
# self.values is not an ndarray.
|
| 254 |
+
index = CategoricalIndex(list("ab") * 5)
|
| 255 |
+
|
| 256 |
+
result = CategoricalIndex(index.values, copy=True)
|
| 257 |
+
tm.assert_index_equal(index, result)
|
| 258 |
+
assert not np.shares_memory(result._data._codes, index._data._codes)
|
| 259 |
+
|
| 260 |
+
result = CategoricalIndex(index.values, copy=False)
|
| 261 |
+
assert result._data._codes is index._data._codes
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
class TestCategoricalIndex2:
|
| 265 |
+
def test_view_i8(self):
|
| 266 |
+
# GH#25464
|
| 267 |
+
ci = CategoricalIndex(list("ab") * 50)
|
| 268 |
+
msg = "When changing to a larger dtype, its size must be a divisor"
|
| 269 |
+
with pytest.raises(ValueError, match=msg):
|
| 270 |
+
ci.view("i8")
|
| 271 |
+
with pytest.raises(ValueError, match=msg):
|
| 272 |
+
ci._data.view("i8")
|
| 273 |
+
|
| 274 |
+
ci = ci[:-4] # length divisible by 8
|
| 275 |
+
|
| 276 |
+
res = ci.view("i8")
|
| 277 |
+
expected = ci._data.codes.view("i8")
|
| 278 |
+
tm.assert_numpy_array_equal(res, expected)
|
| 279 |
+
|
| 280 |
+
cat = ci._data
|
| 281 |
+
tm.assert_numpy_array_equal(cat.view("i8"), expected)
|
| 282 |
+
|
| 283 |
+
@pytest.mark.parametrize(
|
| 284 |
+
"dtype, engine_type",
|
| 285 |
+
[
|
| 286 |
+
(np.int8, libindex.Int8Engine),
|
| 287 |
+
(np.int16, libindex.Int16Engine),
|
| 288 |
+
(np.int32, libindex.Int32Engine),
|
| 289 |
+
(np.int64, libindex.Int64Engine),
|
| 290 |
+
],
|
| 291 |
+
)
|
| 292 |
+
def test_engine_type(self, dtype, engine_type):
|
| 293 |
+
if dtype != np.int64:
|
| 294 |
+
# num. of uniques required to push CategoricalIndex.codes to a
|
| 295 |
+
# dtype (128 categories required for .codes dtype to be int16 etc.)
|
| 296 |
+
num_uniques = {np.int8: 1, np.int16: 128, np.int32: 32768}[dtype]
|
| 297 |
+
ci = CategoricalIndex(range(num_uniques))
|
| 298 |
+
else:
|
| 299 |
+
# having 2**32 - 2**31 categories would be very memory-intensive,
|
| 300 |
+
# so we cheat a bit with the dtype
|
| 301 |
+
ci = CategoricalIndex(range(32768)) # == 2**16 - 2**(16 - 1)
|
| 302 |
+
arr = ci.values._ndarray.astype("int64")
|
| 303 |
+
NDArrayBacked.__init__(ci._data, arr, ci.dtype)
|
| 304 |
+
assert np.issubdtype(ci.codes.dtype, dtype)
|
| 305 |
+
assert isinstance(ci._engine, engine_type)
|
| 306 |
+
|
| 307 |
+
@pytest.mark.parametrize(
|
| 308 |
+
"func,op_name",
|
| 309 |
+
[
|
| 310 |
+
(lambda idx: idx - idx, "__sub__"),
|
| 311 |
+
(lambda idx: idx + idx, "__add__"),
|
| 312 |
+
(lambda idx: idx - ["a", "b"], "__sub__"),
|
| 313 |
+
(lambda idx: idx + ["a", "b"], "__add__"),
|
| 314 |
+
(lambda idx: ["a", "b"] - idx, "__rsub__"),
|
| 315 |
+
(lambda idx: ["a", "b"] + idx, "__radd__"),
|
| 316 |
+
],
|
| 317 |
+
)
|
| 318 |
+
def test_disallow_addsub_ops(self, func, op_name):
|
| 319 |
+
# GH 10039
|
| 320 |
+
# set ops (+/-) raise TypeError
|
| 321 |
+
idx = Index(Categorical(["a", "b"]))
|
| 322 |
+
cat_or_list = "'(Categorical|list)' and '(Categorical|list)'"
|
| 323 |
+
msg = "|".join(
|
| 324 |
+
[
|
| 325 |
+
f"cannot perform {op_name} with this index type: CategoricalIndex",
|
| 326 |
+
"can only concatenate list",
|
| 327 |
+
rf"unsupported operand type\(s\) for [\+-]: {cat_or_list}",
|
| 328 |
+
]
|
| 329 |
+
)
|
| 330 |
+
with pytest.raises(TypeError, match=msg):
|
| 331 |
+
func(idx)
|
| 332 |
+
|
| 333 |
+
def test_method_delegation(self):
|
| 334 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
|
| 335 |
+
result = ci.set_categories(list("cab"))
|
| 336 |
+
tm.assert_index_equal(
|
| 337 |
+
result, CategoricalIndex(list("aabbca"), categories=list("cab"))
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
|
| 341 |
+
result = ci.rename_categories(list("efg"))
|
| 342 |
+
tm.assert_index_equal(
|
| 343 |
+
result, CategoricalIndex(list("ffggef"), categories=list("efg"))
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
# GH18862 (let rename_categories take callables)
|
| 347 |
+
result = ci.rename_categories(lambda x: x.upper())
|
| 348 |
+
tm.assert_index_equal(
|
| 349 |
+
result, CategoricalIndex(list("AABBCA"), categories=list("CAB"))
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
|
| 353 |
+
result = ci.add_categories(["d"])
|
| 354 |
+
tm.assert_index_equal(
|
| 355 |
+
result, CategoricalIndex(list("aabbca"), categories=list("cabd"))
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
|
| 359 |
+
result = ci.remove_categories(["c"])
|
| 360 |
+
tm.assert_index_equal(
|
| 361 |
+
result,
|
| 362 |
+
CategoricalIndex(list("aabb") + [np.nan] + ["a"], categories=list("ab")),
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
|
| 366 |
+
result = ci.as_unordered()
|
| 367 |
+
tm.assert_index_equal(result, ci)
|
| 368 |
+
|
| 369 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
|
| 370 |
+
result = ci.as_ordered()
|
| 371 |
+
tm.assert_index_equal(
|
| 372 |
+
result,
|
| 373 |
+
CategoricalIndex(list("aabbca"), categories=list("cabdef"), ordered=True),
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
# invalid
|
| 377 |
+
msg = "cannot use inplace with CategoricalIndex"
|
| 378 |
+
with pytest.raises(ValueError, match=msg):
|
| 379 |
+
ci.set_categories(list("cab"), inplace=True)
|
| 380 |
+
|
| 381 |
+
def test_remove_maintains_order(self):
|
| 382 |
+
ci = CategoricalIndex(list("abcdda"), categories=list("abcd"))
|
| 383 |
+
result = ci.reorder_categories(["d", "c", "b", "a"], ordered=True)
|
| 384 |
+
tm.assert_index_equal(
|
| 385 |
+
result,
|
| 386 |
+
CategoricalIndex(list("abcdda"), categories=list("dcba"), ordered=True),
|
| 387 |
+
)
|
| 388 |
+
result = result.remove_categories(["c"])
|
| 389 |
+
tm.assert_index_equal(
|
| 390 |
+
result,
|
| 391 |
+
CategoricalIndex(
|
| 392 |
+
["a", "b", np.nan, "d", "d", "a"], categories=list("dba"), ordered=True
|
| 393 |
+
),
|
| 394 |
+
)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_constructors.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
Categorical,
|
| 6 |
+
CategoricalDtype,
|
| 7 |
+
CategoricalIndex,
|
| 8 |
+
Index,
|
| 9 |
+
)
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestCategoricalIndexConstructors:
|
| 14 |
+
def test_construction_disallows_scalar(self):
|
| 15 |
+
msg = "must be called with a collection of some kind"
|
| 16 |
+
with pytest.raises(TypeError, match=msg):
|
| 17 |
+
CategoricalIndex(data=1, categories=list("abcd"), ordered=False)
|
| 18 |
+
with pytest.raises(TypeError, match=msg):
|
| 19 |
+
CategoricalIndex(categories=list("abcd"), ordered=False)
|
| 20 |
+
|
| 21 |
+
def test_construction(self):
|
| 22 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("abcd"), ordered=False)
|
| 23 |
+
categories = ci.categories
|
| 24 |
+
|
| 25 |
+
result = Index(ci)
|
| 26 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 27 |
+
assert not result.ordered
|
| 28 |
+
|
| 29 |
+
result = Index(ci.values)
|
| 30 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 31 |
+
assert not result.ordered
|
| 32 |
+
|
| 33 |
+
# empty
|
| 34 |
+
result = CategoricalIndex([], categories=categories)
|
| 35 |
+
tm.assert_index_equal(result.categories, Index(categories))
|
| 36 |
+
tm.assert_numpy_array_equal(result.codes, np.array([], dtype="int8"))
|
| 37 |
+
assert not result.ordered
|
| 38 |
+
|
| 39 |
+
# passing categories
|
| 40 |
+
result = CategoricalIndex(list("aabbca"), categories=categories)
|
| 41 |
+
tm.assert_index_equal(result.categories, Index(categories))
|
| 42 |
+
tm.assert_numpy_array_equal(
|
| 43 |
+
result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
c = Categorical(list("aabbca"))
|
| 47 |
+
result = CategoricalIndex(c)
|
| 48 |
+
tm.assert_index_equal(result.categories, Index(list("abc")))
|
| 49 |
+
tm.assert_numpy_array_equal(
|
| 50 |
+
result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
|
| 51 |
+
)
|
| 52 |
+
assert not result.ordered
|
| 53 |
+
|
| 54 |
+
result = CategoricalIndex(c, categories=categories)
|
| 55 |
+
tm.assert_index_equal(result.categories, Index(categories))
|
| 56 |
+
tm.assert_numpy_array_equal(
|
| 57 |
+
result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
|
| 58 |
+
)
|
| 59 |
+
assert not result.ordered
|
| 60 |
+
|
| 61 |
+
ci = CategoricalIndex(c, categories=list("abcd"))
|
| 62 |
+
result = CategoricalIndex(ci)
|
| 63 |
+
tm.assert_index_equal(result.categories, Index(categories))
|
| 64 |
+
tm.assert_numpy_array_equal(
|
| 65 |
+
result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
|
| 66 |
+
)
|
| 67 |
+
assert not result.ordered
|
| 68 |
+
|
| 69 |
+
result = CategoricalIndex(ci, categories=list("ab"))
|
| 70 |
+
tm.assert_index_equal(result.categories, Index(list("ab")))
|
| 71 |
+
tm.assert_numpy_array_equal(
|
| 72 |
+
result.codes, np.array([0, 0, 1, 1, -1, 0], dtype="int8")
|
| 73 |
+
)
|
| 74 |
+
assert not result.ordered
|
| 75 |
+
|
| 76 |
+
result = CategoricalIndex(ci, categories=list("ab"), ordered=True)
|
| 77 |
+
tm.assert_index_equal(result.categories, Index(list("ab")))
|
| 78 |
+
tm.assert_numpy_array_equal(
|
| 79 |
+
result.codes, np.array([0, 0, 1, 1, -1, 0], dtype="int8")
|
| 80 |
+
)
|
| 81 |
+
assert result.ordered
|
| 82 |
+
|
| 83 |
+
result = CategoricalIndex(ci, categories=list("ab"), ordered=True)
|
| 84 |
+
expected = CategoricalIndex(
|
| 85 |
+
ci, categories=list("ab"), ordered=True, dtype="category"
|
| 86 |
+
)
|
| 87 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 88 |
+
|
| 89 |
+
# turn me to an Index
|
| 90 |
+
result = Index(np.array(ci))
|
| 91 |
+
assert isinstance(result, Index)
|
| 92 |
+
assert not isinstance(result, CategoricalIndex)
|
| 93 |
+
|
| 94 |
+
def test_construction_with_dtype(self):
|
| 95 |
+
# specify dtype
|
| 96 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("abc"), ordered=False)
|
| 97 |
+
|
| 98 |
+
result = Index(np.array(ci), dtype="category")
|
| 99 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 100 |
+
|
| 101 |
+
result = Index(np.array(ci).tolist(), dtype="category")
|
| 102 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 103 |
+
|
| 104 |
+
# these are generally only equal when the categories are reordered
|
| 105 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
|
| 106 |
+
|
| 107 |
+
result = Index(np.array(ci), dtype="category").reorder_categories(ci.categories)
|
| 108 |
+
tm.assert_index_equal(result, ci, exact=True)
|
| 109 |
+
|
| 110 |
+
# make sure indexes are handled
|
| 111 |
+
idx = Index(range(3))
|
| 112 |
+
expected = CategoricalIndex([0, 1, 2], categories=idx, ordered=True)
|
| 113 |
+
result = CategoricalIndex(idx, categories=idx, ordered=True)
|
| 114 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 115 |
+
|
| 116 |
+
def test_construction_empty_with_bool_categories(self):
|
| 117 |
+
# see GH#22702
|
| 118 |
+
cat = CategoricalIndex([], categories=[True, False])
|
| 119 |
+
categories = sorted(cat.categories.tolist())
|
| 120 |
+
assert categories == [False, True]
|
| 121 |
+
|
| 122 |
+
def test_construction_with_categorical_dtype(self):
|
| 123 |
+
# construction with CategoricalDtype
|
| 124 |
+
# GH#18109
|
| 125 |
+
data, cats, ordered = "a a b b".split(), "c b a".split(), True
|
| 126 |
+
dtype = CategoricalDtype(categories=cats, ordered=ordered)
|
| 127 |
+
|
| 128 |
+
result = CategoricalIndex(data, dtype=dtype)
|
| 129 |
+
expected = CategoricalIndex(data, categories=cats, ordered=ordered)
|
| 130 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 131 |
+
|
| 132 |
+
# GH#19032
|
| 133 |
+
result = Index(data, dtype=dtype)
|
| 134 |
+
tm.assert_index_equal(result, expected, exact=True)
|
| 135 |
+
|
| 136 |
+
# error when combining categories/ordered and dtype kwargs
|
| 137 |
+
msg = "Cannot specify `categories` or `ordered` together with `dtype`."
|
| 138 |
+
with pytest.raises(ValueError, match=msg):
|
| 139 |
+
CategoricalIndex(data, categories=cats, dtype=dtype)
|
| 140 |
+
|
| 141 |
+
with pytest.raises(ValueError, match=msg):
|
| 142 |
+
CategoricalIndex(data, ordered=ordered, dtype=dtype)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_equals.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
Categorical,
|
| 6 |
+
CategoricalIndex,
|
| 7 |
+
Index,
|
| 8 |
+
MultiIndex,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestEquals:
|
| 13 |
+
def test_equals_categorical(self):
|
| 14 |
+
ci1 = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
|
| 15 |
+
ci2 = CategoricalIndex(["a", "b"], categories=["a", "b", "c"], ordered=True)
|
| 16 |
+
|
| 17 |
+
assert ci1.equals(ci1)
|
| 18 |
+
assert not ci1.equals(ci2)
|
| 19 |
+
assert ci1.equals(ci1.astype(object))
|
| 20 |
+
assert ci1.astype(object).equals(ci1)
|
| 21 |
+
|
| 22 |
+
assert (ci1 == ci1).all()
|
| 23 |
+
assert not (ci1 != ci1).all()
|
| 24 |
+
assert not (ci1 > ci1).all()
|
| 25 |
+
assert not (ci1 < ci1).all()
|
| 26 |
+
assert (ci1 <= ci1).all()
|
| 27 |
+
assert (ci1 >= ci1).all()
|
| 28 |
+
|
| 29 |
+
assert not (ci1 == 1).all()
|
| 30 |
+
assert (ci1 == Index(["a", "b"])).all()
|
| 31 |
+
assert (ci1 == ci1.values).all()
|
| 32 |
+
|
| 33 |
+
# invalid comparisons
|
| 34 |
+
with pytest.raises(ValueError, match="Lengths must match"):
|
| 35 |
+
ci1 == Index(["a", "b", "c"])
|
| 36 |
+
|
| 37 |
+
msg = "Categoricals can only be compared if 'categories' are the same"
|
| 38 |
+
with pytest.raises(TypeError, match=msg):
|
| 39 |
+
ci1 == ci2
|
| 40 |
+
with pytest.raises(TypeError, match=msg):
|
| 41 |
+
ci1 == Categorical(ci1.values, ordered=False)
|
| 42 |
+
with pytest.raises(TypeError, match=msg):
|
| 43 |
+
ci1 == Categorical(ci1.values, categories=list("abc"))
|
| 44 |
+
|
| 45 |
+
# tests
|
| 46 |
+
# make sure that we are testing for category inclusion properly
|
| 47 |
+
ci = CategoricalIndex(list("aabca"), categories=["c", "a", "b"])
|
| 48 |
+
assert not ci.equals(list("aabca"))
|
| 49 |
+
# Same categories, but different order
|
| 50 |
+
# Unordered
|
| 51 |
+
assert ci.equals(CategoricalIndex(list("aabca")))
|
| 52 |
+
# Ordered
|
| 53 |
+
assert not ci.equals(CategoricalIndex(list("aabca"), ordered=True))
|
| 54 |
+
assert ci.equals(ci.copy())
|
| 55 |
+
|
| 56 |
+
ci = CategoricalIndex(list("aabca") + [np.nan], categories=["c", "a", "b"])
|
| 57 |
+
assert not ci.equals(list("aabca"))
|
| 58 |
+
assert not ci.equals(CategoricalIndex(list("aabca")))
|
| 59 |
+
assert ci.equals(ci.copy())
|
| 60 |
+
|
| 61 |
+
ci = CategoricalIndex(list("aabca") + [np.nan], categories=["c", "a", "b"])
|
| 62 |
+
assert not ci.equals(list("aabca") + [np.nan])
|
| 63 |
+
assert ci.equals(CategoricalIndex(list("aabca") + [np.nan]))
|
| 64 |
+
assert not ci.equals(CategoricalIndex(list("aabca") + [np.nan], ordered=True))
|
| 65 |
+
assert ci.equals(ci.copy())
|
| 66 |
+
|
| 67 |
+
def test_equals_categorical_unordered(self):
|
| 68 |
+
# https://github.com/pandas-dev/pandas/issues/16603
|
| 69 |
+
a = CategoricalIndex(["A"], categories=["A", "B"])
|
| 70 |
+
b = CategoricalIndex(["A"], categories=["B", "A"])
|
| 71 |
+
c = CategoricalIndex(["C"], categories=["B", "A"])
|
| 72 |
+
assert a.equals(b)
|
| 73 |
+
assert not a.equals(c)
|
| 74 |
+
assert not b.equals(c)
|
| 75 |
+
|
| 76 |
+
def test_equals_non_category(self):
|
| 77 |
+
# GH#37667 Case where other contains a value not among ci's
|
| 78 |
+
# categories ("D") and also contains np.nan
|
| 79 |
+
ci = CategoricalIndex(["A", "B", np.nan, np.nan])
|
| 80 |
+
other = Index(["A", "B", "D", np.nan])
|
| 81 |
+
|
| 82 |
+
assert not ci.equals(other)
|
| 83 |
+
|
| 84 |
+
def test_equals_multiindex(self):
|
| 85 |
+
# dont raise NotImplementedError when calling is_dtype_compat
|
| 86 |
+
|
| 87 |
+
mi = MultiIndex.from_arrays([["A", "B", "C", "D"], range(4)])
|
| 88 |
+
ci = mi.to_flat_index().astype("category")
|
| 89 |
+
|
| 90 |
+
assert not ci.equals(mi)
|
| 91 |
+
|
| 92 |
+
def test_equals_string_dtype(self, any_string_dtype):
|
| 93 |
+
# GH#55364
|
| 94 |
+
idx = CategoricalIndex(list("abc"), name="B")
|
| 95 |
+
other = Index(["a", "b", "c"], name="B", dtype=any_string_dtype)
|
| 96 |
+
assert idx.equals(other)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_fillna.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import CategoricalIndex
|
| 5 |
+
import pandas._testing as tm
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestFillNA:
|
| 9 |
+
def test_fillna_categorical(self):
|
| 10 |
+
# GH#11343
|
| 11 |
+
idx = CategoricalIndex([1.0, np.nan, 3.0, 1.0], name="x")
|
| 12 |
+
# fill by value in categories
|
| 13 |
+
exp = CategoricalIndex([1.0, 1.0, 3.0, 1.0], name="x")
|
| 14 |
+
tm.assert_index_equal(idx.fillna(1.0), exp)
|
| 15 |
+
|
| 16 |
+
cat = idx._data
|
| 17 |
+
|
| 18 |
+
# fill by value not in categories raises TypeError on EA, casts on CI
|
| 19 |
+
msg = "Cannot setitem on a Categorical with a new category"
|
| 20 |
+
with pytest.raises(TypeError, match=msg):
|
| 21 |
+
cat.fillna(2.0)
|
| 22 |
+
|
| 23 |
+
result = idx.fillna(2.0)
|
| 24 |
+
expected = idx.astype(object).fillna(2.0)
|
| 25 |
+
tm.assert_index_equal(result, expected)
|
| 26 |
+
|
| 27 |
+
def test_fillna_copies_with_no_nas(self):
|
| 28 |
+
# Nothing to fill, should still get a copy for the Categorical method,
|
| 29 |
+
# but OK to get a view on CategoricalIndex method
|
| 30 |
+
ci = CategoricalIndex([0, 1, 1])
|
| 31 |
+
result = ci.fillna(0)
|
| 32 |
+
assert result is not ci
|
| 33 |
+
assert tm.shares_memory(result, ci)
|
| 34 |
+
|
| 35 |
+
# But at the EA level we always get a copy.
|
| 36 |
+
cat = ci._data
|
| 37 |
+
result = cat.fillna(0)
|
| 38 |
+
assert result._ndarray is not cat._ndarray
|
| 39 |
+
assert result._ndarray.base is None
|
| 40 |
+
assert not tm.shares_memory(result, cat)
|
| 41 |
+
|
| 42 |
+
def test_fillna_validates_with_no_nas(self):
|
| 43 |
+
# We validate the fill value even if fillna is a no-op
|
| 44 |
+
ci = CategoricalIndex([2, 3, 3])
|
| 45 |
+
cat = ci._data
|
| 46 |
+
|
| 47 |
+
msg = "Cannot setitem on a Categorical with a new category"
|
| 48 |
+
res = ci.fillna(False)
|
| 49 |
+
# nothing to fill, so we dont cast
|
| 50 |
+
tm.assert_index_equal(res, ci)
|
| 51 |
+
|
| 52 |
+
# Same check directly on the Categorical
|
| 53 |
+
with pytest.raises(TypeError, match=msg):
|
| 54 |
+
cat.fillna(False)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_formats.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tests for CategoricalIndex.__repr__ and related methods.
|
| 3 |
+
"""
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from pandas._config import using_pyarrow_string_dtype
|
| 7 |
+
import pandas._config.config as cf
|
| 8 |
+
|
| 9 |
+
from pandas import CategoricalIndex
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestCategoricalIndexRepr:
|
| 14 |
+
def test_format_different_scalar_lengths(self):
|
| 15 |
+
# GH#35439
|
| 16 |
+
idx = CategoricalIndex(["aaaaaaaaa", "b"])
|
| 17 |
+
expected = ["aaaaaaaaa", "b"]
|
| 18 |
+
msg = r"CategoricalIndex\.format is deprecated"
|
| 19 |
+
with tm.assert_produces_warning(FutureWarning, match=msg):
|
| 20 |
+
assert idx.format() == expected
|
| 21 |
+
|
| 22 |
+
@pytest.mark.xfail(using_pyarrow_string_dtype(), reason="repr different")
|
| 23 |
+
def test_string_categorical_index_repr(self):
|
| 24 |
+
# short
|
| 25 |
+
idx = CategoricalIndex(["a", "bb", "ccc"])
|
| 26 |
+
expected = """CategoricalIndex(['a', 'bb', 'ccc'], categories=['a', 'bb', 'ccc'], ordered=False, dtype='category')""" # noqa: E501
|
| 27 |
+
assert repr(idx) == expected
|
| 28 |
+
|
| 29 |
+
# multiple lines
|
| 30 |
+
idx = CategoricalIndex(["a", "bb", "ccc"] * 10)
|
| 31 |
+
expected = """CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
|
| 32 |
+
'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb',
|
| 33 |
+
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
|
| 34 |
+
categories=['a', 'bb', 'ccc'], ordered=False, dtype='category')""" # noqa: E501
|
| 35 |
+
|
| 36 |
+
assert repr(idx) == expected
|
| 37 |
+
|
| 38 |
+
# truncated
|
| 39 |
+
idx = CategoricalIndex(["a", "bb", "ccc"] * 100)
|
| 40 |
+
expected = """CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
|
| 41 |
+
...
|
| 42 |
+
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
|
| 43 |
+
categories=['a', 'bb', 'ccc'], ordered=False, dtype='category', length=300)""" # noqa: E501
|
| 44 |
+
|
| 45 |
+
assert repr(idx) == expected
|
| 46 |
+
|
| 47 |
+
# larger categories
|
| 48 |
+
idx = CategoricalIndex(list("abcdefghijklmmo"))
|
| 49 |
+
expected = """CategoricalIndex(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
| 50 |
+
'm', 'm', 'o'],
|
| 51 |
+
categories=['a', 'b', 'c', 'd', ..., 'k', 'l', 'm', 'o'], ordered=False, dtype='category')""" # noqa: E501
|
| 52 |
+
|
| 53 |
+
assert repr(idx) == expected
|
| 54 |
+
|
| 55 |
+
# short
|
| 56 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"])
|
| 57 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう'], categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
|
| 58 |
+
assert repr(idx) == expected
|
| 59 |
+
|
| 60 |
+
# multiple lines
|
| 61 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"] * 10)
|
| 62 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
|
| 63 |
+
'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
|
| 64 |
+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
|
| 65 |
+
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
|
| 66 |
+
|
| 67 |
+
assert repr(idx) == expected
|
| 68 |
+
|
| 69 |
+
# truncated
|
| 70 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"] * 100)
|
| 71 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
|
| 72 |
+
...
|
| 73 |
+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
|
| 74 |
+
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)""" # noqa: E501
|
| 75 |
+
|
| 76 |
+
assert repr(idx) == expected
|
| 77 |
+
|
| 78 |
+
# larger categories
|
| 79 |
+
idx = CategoricalIndex(list("あいうえおかきくけこさしすせそ"))
|
| 80 |
+
expected = """CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し',
|
| 81 |
+
'す', 'せ', 'そ'],
|
| 82 |
+
categories=['あ', 'い', 'う', 'え', ..., 'し', 'す', 'せ', 'そ'], ordered=False, dtype='category')""" # noqa: E501
|
| 83 |
+
|
| 84 |
+
assert repr(idx) == expected
|
| 85 |
+
|
| 86 |
+
# Enable Unicode option -----------------------------------------
|
| 87 |
+
with cf.option_context("display.unicode.east_asian_width", True):
|
| 88 |
+
# short
|
| 89 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"])
|
| 90 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう'], categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
|
| 91 |
+
assert repr(idx) == expected
|
| 92 |
+
|
| 93 |
+
# multiple lines
|
| 94 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"] * 10)
|
| 95 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
|
| 96 |
+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
|
| 97 |
+
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
|
| 98 |
+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
|
| 99 |
+
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
|
| 100 |
+
|
| 101 |
+
assert repr(idx) == expected
|
| 102 |
+
|
| 103 |
+
# truncated
|
| 104 |
+
idx = CategoricalIndex(["あ", "いい", "ううう"] * 100)
|
| 105 |
+
expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
|
| 106 |
+
'ううう', 'あ',
|
| 107 |
+
...
|
| 108 |
+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
|
| 109 |
+
'あ', 'いい', 'ううう'],
|
| 110 |
+
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)""" # noqa: E501
|
| 111 |
+
|
| 112 |
+
assert repr(idx) == expected
|
| 113 |
+
|
| 114 |
+
# larger categories
|
| 115 |
+
idx = CategoricalIndex(list("あいうえおかきくけこさしすせそ"))
|
| 116 |
+
expected = """CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ',
|
| 117 |
+
'さ', 'し', 'す', 'せ', 'そ'],
|
| 118 |
+
categories=['あ', 'い', 'う', 'え', ..., 'し', 'す', 'せ', 'そ'], ordered=False, dtype='category')""" # noqa: E501
|
| 119 |
+
|
| 120 |
+
assert repr(idx) == expected
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_indexing.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas.errors import InvalidIndexError
|
| 5 |
+
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from pandas import (
|
| 8 |
+
CategoricalIndex,
|
| 9 |
+
Index,
|
| 10 |
+
IntervalIndex,
|
| 11 |
+
Timestamp,
|
| 12 |
+
)
|
| 13 |
+
import pandas._testing as tm
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestTake:
|
| 17 |
+
def test_take_fill_value(self):
|
| 18 |
+
# GH 12631
|
| 19 |
+
|
| 20 |
+
# numeric category
|
| 21 |
+
idx = CategoricalIndex([1, 2, 3], name="xxx")
|
| 22 |
+
result = idx.take(np.array([1, 0, -1]))
|
| 23 |
+
expected = CategoricalIndex([2, 1, 3], name="xxx")
|
| 24 |
+
tm.assert_index_equal(result, expected)
|
| 25 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 26 |
+
|
| 27 |
+
# fill_value
|
| 28 |
+
result = idx.take(np.array([1, 0, -1]), fill_value=True)
|
| 29 |
+
expected = CategoricalIndex([2, 1, np.nan], categories=[1, 2, 3], name="xxx")
|
| 30 |
+
tm.assert_index_equal(result, expected)
|
| 31 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 32 |
+
|
| 33 |
+
# allow_fill=False
|
| 34 |
+
result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
|
| 35 |
+
expected = CategoricalIndex([2, 1, 3], name="xxx")
|
| 36 |
+
tm.assert_index_equal(result, expected)
|
| 37 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 38 |
+
|
| 39 |
+
# object category
|
| 40 |
+
idx = CategoricalIndex(
|
| 41 |
+
list("CBA"), categories=list("ABC"), ordered=True, name="xxx"
|
| 42 |
+
)
|
| 43 |
+
result = idx.take(np.array([1, 0, -1]))
|
| 44 |
+
expected = CategoricalIndex(
|
| 45 |
+
list("BCA"), categories=list("ABC"), ordered=True, name="xxx"
|
| 46 |
+
)
|
| 47 |
+
tm.assert_index_equal(result, expected)
|
| 48 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 49 |
+
|
| 50 |
+
# fill_value
|
| 51 |
+
result = idx.take(np.array([1, 0, -1]), fill_value=True)
|
| 52 |
+
expected = CategoricalIndex(
|
| 53 |
+
["B", "C", np.nan], categories=list("ABC"), ordered=True, name="xxx"
|
| 54 |
+
)
|
| 55 |
+
tm.assert_index_equal(result, expected)
|
| 56 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 57 |
+
|
| 58 |
+
# allow_fill=False
|
| 59 |
+
result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
|
| 60 |
+
expected = CategoricalIndex(
|
| 61 |
+
list("BCA"), categories=list("ABC"), ordered=True, name="xxx"
|
| 62 |
+
)
|
| 63 |
+
tm.assert_index_equal(result, expected)
|
| 64 |
+
tm.assert_categorical_equal(result.values, expected.values)
|
| 65 |
+
|
| 66 |
+
msg = (
|
| 67 |
+
"When allow_fill=True and fill_value is not None, "
|
| 68 |
+
"all indices must be >= -1"
|
| 69 |
+
)
|
| 70 |
+
with pytest.raises(ValueError, match=msg):
|
| 71 |
+
idx.take(np.array([1, 0, -2]), fill_value=True)
|
| 72 |
+
with pytest.raises(ValueError, match=msg):
|
| 73 |
+
idx.take(np.array([1, 0, -5]), fill_value=True)
|
| 74 |
+
|
| 75 |
+
msg = "index -5 is out of bounds for (axis 0 with )?size 3"
|
| 76 |
+
with pytest.raises(IndexError, match=msg):
|
| 77 |
+
idx.take(np.array([1, -5]))
|
| 78 |
+
|
| 79 |
+
def test_take_fill_value_datetime(self):
|
| 80 |
+
# datetime category
|
| 81 |
+
idx = pd.DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"], name="xxx")
|
| 82 |
+
idx = CategoricalIndex(idx)
|
| 83 |
+
result = idx.take(np.array([1, 0, -1]))
|
| 84 |
+
expected = pd.DatetimeIndex(
|
| 85 |
+
["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx"
|
| 86 |
+
)
|
| 87 |
+
expected = CategoricalIndex(expected)
|
| 88 |
+
tm.assert_index_equal(result, expected)
|
| 89 |
+
|
| 90 |
+
# fill_value
|
| 91 |
+
result = idx.take(np.array([1, 0, -1]), fill_value=True)
|
| 92 |
+
expected = pd.DatetimeIndex(["2011-02-01", "2011-01-01", "NaT"], name="xxx")
|
| 93 |
+
exp_cats = pd.DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"])
|
| 94 |
+
expected = CategoricalIndex(expected, categories=exp_cats)
|
| 95 |
+
tm.assert_index_equal(result, expected)
|
| 96 |
+
|
| 97 |
+
# allow_fill=False
|
| 98 |
+
result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
|
| 99 |
+
expected = pd.DatetimeIndex(
|
| 100 |
+
["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx"
|
| 101 |
+
)
|
| 102 |
+
expected = CategoricalIndex(expected)
|
| 103 |
+
tm.assert_index_equal(result, expected)
|
| 104 |
+
|
| 105 |
+
msg = (
|
| 106 |
+
"When allow_fill=True and fill_value is not None, "
|
| 107 |
+
"all indices must be >= -1"
|
| 108 |
+
)
|
| 109 |
+
with pytest.raises(ValueError, match=msg):
|
| 110 |
+
idx.take(np.array([1, 0, -2]), fill_value=True)
|
| 111 |
+
with pytest.raises(ValueError, match=msg):
|
| 112 |
+
idx.take(np.array([1, 0, -5]), fill_value=True)
|
| 113 |
+
|
| 114 |
+
msg = "index -5 is out of bounds for (axis 0 with )?size 3"
|
| 115 |
+
with pytest.raises(IndexError, match=msg):
|
| 116 |
+
idx.take(np.array([1, -5]))
|
| 117 |
+
|
| 118 |
+
def test_take_invalid_kwargs(self):
|
| 119 |
+
idx = CategoricalIndex([1, 2, 3], name="foo")
|
| 120 |
+
indices = [1, 0, -1]
|
| 121 |
+
|
| 122 |
+
msg = r"take\(\) got an unexpected keyword argument 'foo'"
|
| 123 |
+
with pytest.raises(TypeError, match=msg):
|
| 124 |
+
idx.take(indices, foo=2)
|
| 125 |
+
|
| 126 |
+
msg = "the 'out' parameter is not supported"
|
| 127 |
+
with pytest.raises(ValueError, match=msg):
|
| 128 |
+
idx.take(indices, out=indices)
|
| 129 |
+
|
| 130 |
+
msg = "the 'mode' parameter is not supported"
|
| 131 |
+
with pytest.raises(ValueError, match=msg):
|
| 132 |
+
idx.take(indices, mode="clip")
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class TestGetLoc:
|
| 136 |
+
def test_get_loc(self):
|
| 137 |
+
# GH 12531
|
| 138 |
+
cidx1 = CategoricalIndex(list("abcde"), categories=list("edabc"))
|
| 139 |
+
idx1 = Index(list("abcde"))
|
| 140 |
+
assert cidx1.get_loc("a") == idx1.get_loc("a")
|
| 141 |
+
assert cidx1.get_loc("e") == idx1.get_loc("e")
|
| 142 |
+
|
| 143 |
+
for i in [cidx1, idx1]:
|
| 144 |
+
with pytest.raises(KeyError, match="'NOT-EXIST'"):
|
| 145 |
+
i.get_loc("NOT-EXIST")
|
| 146 |
+
|
| 147 |
+
# non-unique
|
| 148 |
+
cidx2 = CategoricalIndex(list("aacded"), categories=list("edabc"))
|
| 149 |
+
idx2 = Index(list("aacded"))
|
| 150 |
+
|
| 151 |
+
# results in bool array
|
| 152 |
+
res = cidx2.get_loc("d")
|
| 153 |
+
tm.assert_numpy_array_equal(res, idx2.get_loc("d"))
|
| 154 |
+
tm.assert_numpy_array_equal(
|
| 155 |
+
res, np.array([False, False, False, True, False, True])
|
| 156 |
+
)
|
| 157 |
+
# unique element results in scalar
|
| 158 |
+
res = cidx2.get_loc("e")
|
| 159 |
+
assert res == idx2.get_loc("e")
|
| 160 |
+
assert res == 4
|
| 161 |
+
|
| 162 |
+
for i in [cidx2, idx2]:
|
| 163 |
+
with pytest.raises(KeyError, match="'NOT-EXIST'"):
|
| 164 |
+
i.get_loc("NOT-EXIST")
|
| 165 |
+
|
| 166 |
+
# non-unique, sliceable
|
| 167 |
+
cidx3 = CategoricalIndex(list("aabbb"), categories=list("abc"))
|
| 168 |
+
idx3 = Index(list("aabbb"))
|
| 169 |
+
|
| 170 |
+
# results in slice
|
| 171 |
+
res = cidx3.get_loc("a")
|
| 172 |
+
assert res == idx3.get_loc("a")
|
| 173 |
+
assert res == slice(0, 2, None)
|
| 174 |
+
|
| 175 |
+
res = cidx3.get_loc("b")
|
| 176 |
+
assert res == idx3.get_loc("b")
|
| 177 |
+
assert res == slice(2, 5, None)
|
| 178 |
+
|
| 179 |
+
for i in [cidx3, idx3]:
|
| 180 |
+
with pytest.raises(KeyError, match="'c'"):
|
| 181 |
+
i.get_loc("c")
|
| 182 |
+
|
| 183 |
+
def test_get_loc_unique(self):
|
| 184 |
+
cidx = CategoricalIndex(list("abc"))
|
| 185 |
+
result = cidx.get_loc("b")
|
| 186 |
+
assert result == 1
|
| 187 |
+
|
| 188 |
+
def test_get_loc_monotonic_nonunique(self):
|
| 189 |
+
cidx = CategoricalIndex(list("abbc"))
|
| 190 |
+
result = cidx.get_loc("b")
|
| 191 |
+
expected = slice(1, 3, None)
|
| 192 |
+
assert result == expected
|
| 193 |
+
|
| 194 |
+
def test_get_loc_nonmonotonic_nonunique(self):
|
| 195 |
+
cidx = CategoricalIndex(list("abcb"))
|
| 196 |
+
result = cidx.get_loc("b")
|
| 197 |
+
expected = np.array([False, True, False, True], dtype=bool)
|
| 198 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 199 |
+
|
| 200 |
+
def test_get_loc_nan(self):
|
| 201 |
+
# GH#41933
|
| 202 |
+
ci = CategoricalIndex(["A", "B", np.nan])
|
| 203 |
+
res = ci.get_loc(np.nan)
|
| 204 |
+
|
| 205 |
+
assert res == 2
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class TestGetIndexer:
|
| 209 |
+
def test_get_indexer_base(self):
|
| 210 |
+
# Determined by cat ordering.
|
| 211 |
+
idx = CategoricalIndex(list("cab"), categories=list("cab"))
|
| 212 |
+
expected = np.arange(len(idx), dtype=np.intp)
|
| 213 |
+
|
| 214 |
+
actual = idx.get_indexer(idx)
|
| 215 |
+
tm.assert_numpy_array_equal(expected, actual)
|
| 216 |
+
|
| 217 |
+
with pytest.raises(ValueError, match="Invalid fill method"):
|
| 218 |
+
idx.get_indexer(idx, method="invalid")
|
| 219 |
+
|
| 220 |
+
def test_get_indexer_requires_unique(self):
|
| 221 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
|
| 222 |
+
oidx = Index(np.array(ci))
|
| 223 |
+
|
| 224 |
+
msg = "Reindexing only valid with uniquely valued Index objects"
|
| 225 |
+
|
| 226 |
+
for n in [1, 2, 5, len(ci)]:
|
| 227 |
+
finder = oidx[np.random.default_rng(2).integers(0, len(ci), size=n)]
|
| 228 |
+
|
| 229 |
+
with pytest.raises(InvalidIndexError, match=msg):
|
| 230 |
+
ci.get_indexer(finder)
|
| 231 |
+
|
| 232 |
+
# see gh-17323
|
| 233 |
+
#
|
| 234 |
+
# Even when indexer is equal to the
|
| 235 |
+
# members in the index, we should
|
| 236 |
+
# respect duplicates instead of taking
|
| 237 |
+
# the fast-track path.
|
| 238 |
+
for finder in [list("aabbca"), list("aababca")]:
|
| 239 |
+
with pytest.raises(InvalidIndexError, match=msg):
|
| 240 |
+
ci.get_indexer(finder)
|
| 241 |
+
|
| 242 |
+
def test_get_indexer_non_unique(self):
|
| 243 |
+
idx1 = CategoricalIndex(list("aabcde"), categories=list("edabc"))
|
| 244 |
+
idx2 = CategoricalIndex(list("abf"))
|
| 245 |
+
|
| 246 |
+
for indexer in [idx2, list("abf"), Index(list("abf"))]:
|
| 247 |
+
msg = "Reindexing only valid with uniquely valued Index objects"
|
| 248 |
+
with pytest.raises(InvalidIndexError, match=msg):
|
| 249 |
+
idx1.get_indexer(indexer)
|
| 250 |
+
|
| 251 |
+
r1, _ = idx1.get_indexer_non_unique(indexer)
|
| 252 |
+
expected = np.array([0, 1, 2, -1], dtype=np.intp)
|
| 253 |
+
tm.assert_almost_equal(r1, expected)
|
| 254 |
+
|
| 255 |
+
def test_get_indexer_method(self):
|
| 256 |
+
idx1 = CategoricalIndex(list("aabcde"), categories=list("edabc"))
|
| 257 |
+
idx2 = CategoricalIndex(list("abf"))
|
| 258 |
+
|
| 259 |
+
msg = "method pad not yet implemented for CategoricalIndex"
|
| 260 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 261 |
+
idx2.get_indexer(idx1, method="pad")
|
| 262 |
+
msg = "method backfill not yet implemented for CategoricalIndex"
|
| 263 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 264 |
+
idx2.get_indexer(idx1, method="backfill")
|
| 265 |
+
|
| 266 |
+
msg = "method nearest not yet implemented for CategoricalIndex"
|
| 267 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 268 |
+
idx2.get_indexer(idx1, method="nearest")
|
| 269 |
+
|
| 270 |
+
def test_get_indexer_array(self):
|
| 271 |
+
arr = np.array(
|
| 272 |
+
[Timestamp("1999-12-31 00:00:00"), Timestamp("2000-12-31 00:00:00")],
|
| 273 |
+
dtype=object,
|
| 274 |
+
)
|
| 275 |
+
cats = [Timestamp("1999-12-31 00:00:00"), Timestamp("2000-12-31 00:00:00")]
|
| 276 |
+
ci = CategoricalIndex(cats, categories=cats, ordered=False, dtype="category")
|
| 277 |
+
result = ci.get_indexer(arr)
|
| 278 |
+
expected = np.array([0, 1], dtype="intp")
|
| 279 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 280 |
+
|
| 281 |
+
def test_get_indexer_same_categories_same_order(self):
|
| 282 |
+
ci = CategoricalIndex(["a", "b"], categories=["a", "b"])
|
| 283 |
+
|
| 284 |
+
result = ci.get_indexer(CategoricalIndex(["b", "b"], categories=["a", "b"]))
|
| 285 |
+
expected = np.array([1, 1], dtype="intp")
|
| 286 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 287 |
+
|
| 288 |
+
def test_get_indexer_same_categories_different_order(self):
|
| 289 |
+
# https://github.com/pandas-dev/pandas/issues/19551
|
| 290 |
+
ci = CategoricalIndex(["a", "b"], categories=["a", "b"])
|
| 291 |
+
|
| 292 |
+
result = ci.get_indexer(CategoricalIndex(["b", "b"], categories=["b", "a"]))
|
| 293 |
+
expected = np.array([1, 1], dtype="intp")
|
| 294 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 295 |
+
|
| 296 |
+
def test_get_indexer_nans_in_index_and_target(self):
|
| 297 |
+
# GH 45361
|
| 298 |
+
ci = CategoricalIndex([1, 2, np.nan, 3])
|
| 299 |
+
other1 = [2, 3, 4, np.nan]
|
| 300 |
+
res1 = ci.get_indexer(other1)
|
| 301 |
+
expected1 = np.array([1, 3, -1, 2], dtype=np.intp)
|
| 302 |
+
tm.assert_numpy_array_equal(res1, expected1)
|
| 303 |
+
other2 = [1, 4, 2, 3]
|
| 304 |
+
res2 = ci.get_indexer(other2)
|
| 305 |
+
expected2 = np.array([0, -1, 1, 3], dtype=np.intp)
|
| 306 |
+
tm.assert_numpy_array_equal(res2, expected2)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class TestWhere:
|
| 310 |
+
def test_where(self, listlike_box):
|
| 311 |
+
klass = listlike_box
|
| 312 |
+
|
| 313 |
+
i = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
|
| 314 |
+
cond = [True] * len(i)
|
| 315 |
+
expected = i
|
| 316 |
+
result = i.where(klass(cond))
|
| 317 |
+
tm.assert_index_equal(result, expected)
|
| 318 |
+
|
| 319 |
+
cond = [False] + [True] * (len(i) - 1)
|
| 320 |
+
expected = CategoricalIndex([np.nan] + i[1:].tolist(), categories=i.categories)
|
| 321 |
+
result = i.where(klass(cond))
|
| 322 |
+
tm.assert_index_equal(result, expected)
|
| 323 |
+
|
| 324 |
+
def test_where_non_categories(self):
|
| 325 |
+
ci = CategoricalIndex(["a", "b", "c", "d"])
|
| 326 |
+
mask = np.array([True, False, True, False])
|
| 327 |
+
|
| 328 |
+
result = ci.where(mask, 2)
|
| 329 |
+
expected = Index(["a", 2, "c", 2], dtype=object)
|
| 330 |
+
tm.assert_index_equal(result, expected)
|
| 331 |
+
|
| 332 |
+
msg = "Cannot setitem on a Categorical with a new category"
|
| 333 |
+
with pytest.raises(TypeError, match=msg):
|
| 334 |
+
# Test the Categorical method directly
|
| 335 |
+
ci._data._where(mask, 2)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class TestContains:
|
| 339 |
+
def test_contains(self):
|
| 340 |
+
ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"), ordered=False)
|
| 341 |
+
|
| 342 |
+
assert "a" in ci
|
| 343 |
+
assert "z" not in ci
|
| 344 |
+
assert "e" not in ci
|
| 345 |
+
assert np.nan not in ci
|
| 346 |
+
|
| 347 |
+
# assert codes NOT in index
|
| 348 |
+
assert 0 not in ci
|
| 349 |
+
assert 1 not in ci
|
| 350 |
+
|
| 351 |
+
def test_contains_nan(self):
|
| 352 |
+
ci = CategoricalIndex(list("aabbca") + [np.nan], categories=list("cabdef"))
|
| 353 |
+
assert np.nan in ci
|
| 354 |
+
|
| 355 |
+
@pytest.mark.parametrize("unwrap", [True, False])
|
| 356 |
+
def test_contains_na_dtype(self, unwrap):
|
| 357 |
+
dti = pd.date_range("2016-01-01", periods=100).insert(0, pd.NaT)
|
| 358 |
+
pi = dti.to_period("D")
|
| 359 |
+
tdi = dti - dti[-1]
|
| 360 |
+
ci = CategoricalIndex(dti)
|
| 361 |
+
|
| 362 |
+
obj = ci
|
| 363 |
+
if unwrap:
|
| 364 |
+
obj = ci._data
|
| 365 |
+
|
| 366 |
+
assert np.nan in obj
|
| 367 |
+
assert None in obj
|
| 368 |
+
assert pd.NaT in obj
|
| 369 |
+
assert np.datetime64("NaT") in obj
|
| 370 |
+
assert np.timedelta64("NaT") not in obj
|
| 371 |
+
|
| 372 |
+
obj2 = CategoricalIndex(tdi)
|
| 373 |
+
if unwrap:
|
| 374 |
+
obj2 = obj2._data
|
| 375 |
+
|
| 376 |
+
assert np.nan in obj2
|
| 377 |
+
assert None in obj2
|
| 378 |
+
assert pd.NaT in obj2
|
| 379 |
+
assert np.datetime64("NaT") not in obj2
|
| 380 |
+
assert np.timedelta64("NaT") in obj2
|
| 381 |
+
|
| 382 |
+
obj3 = CategoricalIndex(pi)
|
| 383 |
+
if unwrap:
|
| 384 |
+
obj3 = obj3._data
|
| 385 |
+
|
| 386 |
+
assert np.nan in obj3
|
| 387 |
+
assert None in obj3
|
| 388 |
+
assert pd.NaT in obj3
|
| 389 |
+
assert np.datetime64("NaT") not in obj3
|
| 390 |
+
assert np.timedelta64("NaT") not in obj3
|
| 391 |
+
|
| 392 |
+
@pytest.mark.parametrize(
|
| 393 |
+
"item, expected",
|
| 394 |
+
[
|
| 395 |
+
(pd.Interval(0, 1), True),
|
| 396 |
+
(1.5, True),
|
| 397 |
+
(pd.Interval(0.5, 1.5), False),
|
| 398 |
+
("a", False),
|
| 399 |
+
(Timestamp(1), False),
|
| 400 |
+
(pd.Timedelta(1), False),
|
| 401 |
+
],
|
| 402 |
+
ids=str,
|
| 403 |
+
)
|
| 404 |
+
def test_contains_interval(self, item, expected):
|
| 405 |
+
# GH 23705
|
| 406 |
+
ci = CategoricalIndex(IntervalIndex.from_breaks(range(3)))
|
| 407 |
+
result = item in ci
|
| 408 |
+
assert result is expected
|
| 409 |
+
|
| 410 |
+
def test_contains_list(self):
|
| 411 |
+
# GH#21729
|
| 412 |
+
idx = CategoricalIndex([1, 2, 3])
|
| 413 |
+
|
| 414 |
+
assert "a" not in idx
|
| 415 |
+
|
| 416 |
+
with pytest.raises(TypeError, match="unhashable type"):
|
| 417 |
+
["a"] in idx
|
| 418 |
+
|
| 419 |
+
with pytest.raises(TypeError, match="unhashable type"):
|
| 420 |
+
["a", "b"] in idx
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_map.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from pandas import (
|
| 6 |
+
CategoricalIndex,
|
| 7 |
+
Index,
|
| 8 |
+
Series,
|
| 9 |
+
)
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@pytest.mark.parametrize(
|
| 14 |
+
"data, categories",
|
| 15 |
+
[
|
| 16 |
+
(list("abcbca"), list("cab")),
|
| 17 |
+
(pd.interval_range(0, 3).repeat(3), pd.interval_range(0, 3)),
|
| 18 |
+
],
|
| 19 |
+
ids=["string", "interval"],
|
| 20 |
+
)
|
| 21 |
+
def test_map_str(data, categories, ordered):
|
| 22 |
+
# GH 31202 - override base class since we want to maintain categorical/ordered
|
| 23 |
+
index = CategoricalIndex(data, categories=categories, ordered=ordered)
|
| 24 |
+
result = index.map(str)
|
| 25 |
+
expected = CategoricalIndex(
|
| 26 |
+
map(str, data), categories=map(str, categories), ordered=ordered
|
| 27 |
+
)
|
| 28 |
+
tm.assert_index_equal(result, expected)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_map():
|
| 32 |
+
ci = CategoricalIndex(list("ABABC"), categories=list("CBA"), ordered=True)
|
| 33 |
+
result = ci.map(lambda x: x.lower())
|
| 34 |
+
exp = CategoricalIndex(list("ababc"), categories=list("cba"), ordered=True)
|
| 35 |
+
tm.assert_index_equal(result, exp)
|
| 36 |
+
|
| 37 |
+
ci = CategoricalIndex(
|
| 38 |
+
list("ABABC"), categories=list("BAC"), ordered=False, name="XXX"
|
| 39 |
+
)
|
| 40 |
+
result = ci.map(lambda x: x.lower())
|
| 41 |
+
exp = CategoricalIndex(
|
| 42 |
+
list("ababc"), categories=list("bac"), ordered=False, name="XXX"
|
| 43 |
+
)
|
| 44 |
+
tm.assert_index_equal(result, exp)
|
| 45 |
+
|
| 46 |
+
# GH 12766: Return an index not an array
|
| 47 |
+
tm.assert_index_equal(
|
| 48 |
+
ci.map(lambda x: 1), Index(np.array([1] * 5, dtype=np.int64), name="XXX")
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# change categories dtype
|
| 52 |
+
ci = CategoricalIndex(list("ABABC"), categories=list("BAC"), ordered=False)
|
| 53 |
+
|
| 54 |
+
def f(x):
|
| 55 |
+
return {"A": 10, "B": 20, "C": 30}.get(x)
|
| 56 |
+
|
| 57 |
+
result = ci.map(f)
|
| 58 |
+
exp = CategoricalIndex([10, 20, 10, 20, 30], categories=[20, 10, 30], ordered=False)
|
| 59 |
+
tm.assert_index_equal(result, exp)
|
| 60 |
+
|
| 61 |
+
result = ci.map(Series([10, 20, 30], index=["A", "B", "C"]))
|
| 62 |
+
tm.assert_index_equal(result, exp)
|
| 63 |
+
|
| 64 |
+
result = ci.map({"A": 10, "B": 20, "C": 30})
|
| 65 |
+
tm.assert_index_equal(result, exp)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def test_map_with_categorical_series():
|
| 69 |
+
# GH 12756
|
| 70 |
+
a = Index([1, 2, 3, 4])
|
| 71 |
+
b = Series(["even", "odd", "even", "odd"], dtype="category")
|
| 72 |
+
c = Series(["even", "odd", "even", "odd"])
|
| 73 |
+
|
| 74 |
+
exp = CategoricalIndex(["odd", "even", "odd", np.nan])
|
| 75 |
+
tm.assert_index_equal(a.map(b), exp)
|
| 76 |
+
exp = Index(["odd", "even", "odd", np.nan])
|
| 77 |
+
tm.assert_index_equal(a.map(c), exp)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
@pytest.mark.parametrize(
|
| 81 |
+
("data", "f", "expected"),
|
| 82 |
+
(
|
| 83 |
+
([1, 1, np.nan], pd.isna, CategoricalIndex([False, False, np.nan])),
|
| 84 |
+
([1, 2, np.nan], pd.isna, Index([False, False, np.nan])),
|
| 85 |
+
([1, 1, np.nan], {1: False}, CategoricalIndex([False, False, np.nan])),
|
| 86 |
+
([1, 2, np.nan], {1: False, 2: False}, Index([False, False, np.nan])),
|
| 87 |
+
(
|
| 88 |
+
[1, 1, np.nan],
|
| 89 |
+
Series([False, False]),
|
| 90 |
+
CategoricalIndex([False, False, np.nan]),
|
| 91 |
+
),
|
| 92 |
+
(
|
| 93 |
+
[1, 2, np.nan],
|
| 94 |
+
Series([False, False, False]),
|
| 95 |
+
Index([False, False, np.nan]),
|
| 96 |
+
),
|
| 97 |
+
),
|
| 98 |
+
)
|
| 99 |
+
def test_map_with_nan_ignore(data, f, expected): # GH 24241
|
| 100 |
+
values = CategoricalIndex(data)
|
| 101 |
+
result = values.map(f, na_action="ignore")
|
| 102 |
+
tm.assert_index_equal(result, expected)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@pytest.mark.parametrize(
|
| 106 |
+
("data", "f", "expected"),
|
| 107 |
+
(
|
| 108 |
+
([1, 1, np.nan], pd.isna, Index([False, False, True])),
|
| 109 |
+
([1, 2, np.nan], pd.isna, Index([False, False, True])),
|
| 110 |
+
([1, 1, np.nan], {1: False}, CategoricalIndex([False, False, np.nan])),
|
| 111 |
+
([1, 2, np.nan], {1: False, 2: False}, Index([False, False, np.nan])),
|
| 112 |
+
(
|
| 113 |
+
[1, 1, np.nan],
|
| 114 |
+
Series([False, False]),
|
| 115 |
+
CategoricalIndex([False, False, np.nan]),
|
| 116 |
+
),
|
| 117 |
+
(
|
| 118 |
+
[1, 2, np.nan],
|
| 119 |
+
Series([False, False, False]),
|
| 120 |
+
Index([False, False, np.nan]),
|
| 121 |
+
),
|
| 122 |
+
),
|
| 123 |
+
)
|
| 124 |
+
def test_map_with_nan_none(data, f, expected): # GH 24241
|
| 125 |
+
values = CategoricalIndex(data)
|
| 126 |
+
result = values.map(f, na_action=None)
|
| 127 |
+
tm.assert_index_equal(result, expected)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def test_map_with_dict_or_series():
|
| 131 |
+
orig_values = ["a", "B", 1, "a"]
|
| 132 |
+
new_values = ["one", 2, 3.0, "one"]
|
| 133 |
+
cur_index = CategoricalIndex(orig_values, name="XXX")
|
| 134 |
+
expected = CategoricalIndex(new_values, name="XXX", categories=[3.0, 2, "one"])
|
| 135 |
+
|
| 136 |
+
mapper = Series(new_values[:-1], index=orig_values[:-1])
|
| 137 |
+
result = cur_index.map(mapper)
|
| 138 |
+
# Order of categories in result can be different
|
| 139 |
+
tm.assert_index_equal(result, expected)
|
| 140 |
+
|
| 141 |
+
mapper = dict(zip(orig_values[:-1], new_values[:-1]))
|
| 142 |
+
result = cur_index.map(mapper)
|
| 143 |
+
# Order of categories in result can be different
|
| 144 |
+
tm.assert_index_equal(result, expected)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_reindex.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
Categorical,
|
| 6 |
+
CategoricalIndex,
|
| 7 |
+
Index,
|
| 8 |
+
Interval,
|
| 9 |
+
)
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestReindex:
|
| 14 |
+
def test_reindex_list_non_unique(self):
|
| 15 |
+
# GH#11586
|
| 16 |
+
msg = "cannot reindex on an axis with duplicate labels"
|
| 17 |
+
ci = CategoricalIndex(["a", "b", "c", "a"])
|
| 18 |
+
with pytest.raises(ValueError, match=msg):
|
| 19 |
+
ci.reindex(["a", "c"])
|
| 20 |
+
|
| 21 |
+
def test_reindex_categorical_non_unique(self):
|
| 22 |
+
msg = "cannot reindex on an axis with duplicate labels"
|
| 23 |
+
ci = CategoricalIndex(["a", "b", "c", "a"])
|
| 24 |
+
with pytest.raises(ValueError, match=msg):
|
| 25 |
+
ci.reindex(Categorical(["a", "c"]))
|
| 26 |
+
|
| 27 |
+
def test_reindex_list_non_unique_unused_category(self):
|
| 28 |
+
msg = "cannot reindex on an axis with duplicate labels"
|
| 29 |
+
ci = CategoricalIndex(["a", "b", "c", "a"], categories=["a", "b", "c", "d"])
|
| 30 |
+
with pytest.raises(ValueError, match=msg):
|
| 31 |
+
ci.reindex(["a", "c"])
|
| 32 |
+
|
| 33 |
+
def test_reindex_categorical_non_unique_unused_category(self):
|
| 34 |
+
msg = "cannot reindex on an axis with duplicate labels"
|
| 35 |
+
ci = CategoricalIndex(["a", "b", "c", "a"], categories=["a", "b", "c", "d"])
|
| 36 |
+
with pytest.raises(ValueError, match=msg):
|
| 37 |
+
ci.reindex(Categorical(["a", "c"]))
|
| 38 |
+
|
| 39 |
+
def test_reindex_duplicate_target(self):
|
| 40 |
+
# See GH25459
|
| 41 |
+
cat = CategoricalIndex(["a", "b", "c"], categories=["a", "b", "c", "d"])
|
| 42 |
+
res, indexer = cat.reindex(["a", "c", "c"])
|
| 43 |
+
exp = Index(["a", "c", "c"])
|
| 44 |
+
tm.assert_index_equal(res, exp, exact=True)
|
| 45 |
+
tm.assert_numpy_array_equal(indexer, np.array([0, 2, 2], dtype=np.intp))
|
| 46 |
+
|
| 47 |
+
res, indexer = cat.reindex(
|
| 48 |
+
CategoricalIndex(["a", "c", "c"], categories=["a", "b", "c", "d"])
|
| 49 |
+
)
|
| 50 |
+
exp = CategoricalIndex(["a", "c", "c"], categories=["a", "b", "c", "d"])
|
| 51 |
+
tm.assert_index_equal(res, exp, exact=True)
|
| 52 |
+
tm.assert_numpy_array_equal(indexer, np.array([0, 2, 2], dtype=np.intp))
|
| 53 |
+
|
| 54 |
+
def test_reindex_empty_index(self):
|
| 55 |
+
# See GH16770
|
| 56 |
+
c = CategoricalIndex([])
|
| 57 |
+
res, indexer = c.reindex(["a", "b"])
|
| 58 |
+
tm.assert_index_equal(res, Index(["a", "b"]), exact=True)
|
| 59 |
+
tm.assert_numpy_array_equal(indexer, np.array([-1, -1], dtype=np.intp))
|
| 60 |
+
|
| 61 |
+
def test_reindex_categorical_added_category(self):
|
| 62 |
+
# GH 42424
|
| 63 |
+
ci = CategoricalIndex(
|
| 64 |
+
[Interval(0, 1, closed="right"), Interval(1, 2, closed="right")],
|
| 65 |
+
ordered=True,
|
| 66 |
+
)
|
| 67 |
+
ci_add = CategoricalIndex(
|
| 68 |
+
[
|
| 69 |
+
Interval(0, 1, closed="right"),
|
| 70 |
+
Interval(1, 2, closed="right"),
|
| 71 |
+
Interval(2, 3, closed="right"),
|
| 72 |
+
Interval(3, 4, closed="right"),
|
| 73 |
+
],
|
| 74 |
+
ordered=True,
|
| 75 |
+
)
|
| 76 |
+
result, _ = ci.reindex(ci_add)
|
| 77 |
+
expected = ci_add
|
| 78 |
+
tm.assert_index_equal(expected, result)
|
lib/python3.12/site-packages/pandas/tests/indexes/categorical/test_setops.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
CategoricalIndex,
|
| 6 |
+
Index,
|
| 7 |
+
)
|
| 8 |
+
import pandas._testing as tm
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@pytest.mark.parametrize("na_value", [None, np.nan])
|
| 12 |
+
def test_difference_with_na(na_value):
|
| 13 |
+
# GH 57318
|
| 14 |
+
ci = CategoricalIndex(["a", "b", "c", None])
|
| 15 |
+
other = Index(["c", na_value])
|
| 16 |
+
result = ci.difference(other)
|
| 17 |
+
expected = CategoricalIndex(["a", "b"], categories=["a", "b", "c"])
|
| 18 |
+
tm.assert_index_equal(result, expected)
|
lib/python3.12/site-packages/pandas/tests/indexes/conftest.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
Series,
|
| 6 |
+
array,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@pytest.fixture(params=[None, False])
|
| 11 |
+
def sort(request):
|
| 12 |
+
"""
|
| 13 |
+
Valid values for the 'sort' parameter used in the Index
|
| 14 |
+
setops methods (intersection, union, etc.)
|
| 15 |
+
|
| 16 |
+
Caution:
|
| 17 |
+
Don't confuse this one with the "sort" fixture used
|
| 18 |
+
for DataFrame.append or concat. That one has
|
| 19 |
+
parameters [True, False].
|
| 20 |
+
|
| 21 |
+
We can't combine them as sort=True is not permitted
|
| 22 |
+
in the Index setops methods.
|
| 23 |
+
"""
|
| 24 |
+
return request.param
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@pytest.fixture(params=["D", "3D", "-3D", "h", "2h", "-2h", "min", "2min", "s", "-3s"])
|
| 28 |
+
def freq_sample(request):
|
| 29 |
+
"""
|
| 30 |
+
Valid values for 'freq' parameter used to create date_range and
|
| 31 |
+
timedelta_range..
|
| 32 |
+
"""
|
| 33 |
+
return request.param
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@pytest.fixture(params=[list, tuple, np.array, array, Series])
|
| 37 |
+
def listlike_box(request):
|
| 38 |
+
"""
|
| 39 |
+
Types that may be passed as the indexer to searchsorted.
|
| 40 |
+
"""
|
| 41 |
+
return request.param
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (209 Bytes). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_arithmetic.cpython-312.pyc
ADDED
|
Binary file (2.75 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_constructors.cpython-312.pyc
ADDED
|
Binary file (57.3 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_date_range.cpython-312.pyc
ADDED
|
Binary file (79.7 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_datetime.cpython-312.pyc
ADDED
|
Binary file (10.8 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_formats.cpython-312.pyc
ADDED
|
Binary file (17.2 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_freq_attr.cpython-312.pyc
ADDED
|
Binary file (2.83 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_indexing.cpython-312.pyc
ADDED
|
Binary file (41.4 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_iter.cpython-312.pyc
ADDED
|
Binary file (4.13 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_join.cpython-312.pyc
ADDED
|
Binary file (8.25 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_npfuncs.cpython-312.pyc
ADDED
|
Binary file (1.02 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_ops.cpython-312.pyc
ADDED
|
Binary file (3.07 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_partial_slicing.cpython-312.pyc
ADDED
|
Binary file (23 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_pickle.cpython-312.pyc
ADDED
|
Binary file (2.69 kB). View file
|
|
|
lib/python3.12/site-packages/pandas/tests/indexes/datetimes/__pycache__/test_reindex.cpython-312.pyc
ADDED
|
Binary file (2.96 kB). View file
|
|
|